Простое решение с использованием программирования функций «карри» для решения N уровней циклов.
Вам приходилось перебирать сложную структуру данных, чтобы получить результат?
Например, представьте, что вам нужно вызывать такие циклы:
for country in countries: for state in states: for city in cities: for school in schools: call_my_func(my_args)
Давайте попробуем решить простейшую ситуацию, предполагая, что у нас есть только один уровень, и нам просто нужно выполнить цикл следующим образом:
for (let i = 0; i < n; i++){ console.log("hello", "world") }
Если мы поместим его в функцию «карри», он должен выглядеть так:
function loop(n, func){ return (...args) => { let i = 0 while(i < n){ func(...args) i ++ } } } l3 = loop(3, (...arg)=> console.log(...arg)) l3("hello", "world")
Функция цикла возвращает функцию, которая просто выполнит цикл 3 раза и напечатает 3 строки «hello world».
Это не так уж и плохо, давайте улучшим его, чтобы он также мог использовать m уровень петель
Что-нибудь для имитации следующего
for (let i0 = 0; i0 < n; i0++){ for (let i1= 0; i1 < n; i1++){ for (let i2 = 0; i2 < n; i2++){ ... ... for (let im = 0; im < n; im++){ console.log("hello", "world") }
Как мы решаем эту проблему? Нам нужен массив для хранения значений каждого i (i0, i1… im). Мы можем назвать это я []
Логика заключается в том, что сначала мы увеличиваем i [m], затем если i [m] ≥ n, то мы увеличиваем i [m-1] и продолжайте движение до тех пор, пока не станет i [0] ≥ n
Теперь переведем логику в код, это выглядит так
// Create an m-items array and fill with 0 let i = Array(m).fill(0) function inc(j){ i[j] += 1 if(i[j] >= n){ if(j <= 0) return i[j] = 0 inc(j - 1) } }
Функция inc (j) будет увеличиваться до тех пор, пока не станет больше, чем n, затем будет увеличиваться (j-1) до первого слоя (a [0]) ≥ n, затем рекурсивная функция возвращает
Теперь мы можем вернуться к нашему вопросу выше, чтобы реализовать функцию, которая может обрабатывать уровень циклов m.
function loop(n,m, func){ return (...args) => { let i = Array(m).fill(0) function inc(j){ i[j] += 1 if(i[j] >= n){ if(j <= 0) return i[j] = 0 inc(j - 1) } } while(i[0] < n){ func(...args) console.log(i) console.log() inc(m - 1) } } } l4 = loop(2,2, (...arg)=> console.log(...arg)) l4("hello", "world")
Приведенная выше функция напечатает такой результат
hello world [ 0, 0 ] hello world [ 0, 1 ] hello world [ 1, 0 ] hello world [ 1, 1 ]
Вы можете попробовать с разными числами n, m, и в основном вы получите n ^ m (m степень n) строк «hello world» :)
Теперь мы накопили достаточно опыта, чтобы решить последний вопрос - перебрать произвольный массив «циклов».
[ "0:2:1", "0:4:2", "1:9:4" ]
Вышеупомянутый массив пытается представить такую логику
for(let i0 = 0; i0 < 2; i0 ++) for(let i1 = 0; i1 < 4; i1 += 2) for(let i2 = 0; i2 < 9; i2 += 4)
Строки в массиве предназначены для имитации синтаксиса Python «Slice».
Slice означает, что первое число - это «начало», второе число - «конец», а третье число - «шаг» (шаг по умолчанию равен 1).
Вот и последняя версия решения, которое просто объединяет все, что у нас было до сих пор.
function loop(slices, func){ return (...args) => { let m = slices.length let a = Array(m).fill({}) for(let i = 0; i < m; i++ ){ let [start, end, step] = slices[i].split(":").map(x => parseInt(x)) step = step || 1 a[i] = { start, end, step, cur: start } } function inc(j){ a[j].cur += a[j].step if(a[j].cur >= a[j].end){ if(j <= 0) return a[j].cur = a[j].start inc(j - 1) } } while(a[0].cur < a[0].end){ func(...args) console.log(a) console.log() inc(m - 1) } } } l5 = loop([ "0:2", "0:4:2", "1:9:4" ], (...arg)=> console.log(...arg)) l5("hello", "world")
Теперь у нас есть удобная функция, которая может «картировать» служебную функцию для прохождения любого количества слоев циклов.
Вы можете играть в них онлайн, используя https://repl.it/languages/javascript.
Спасибо за чтение и удачного кодирования :)