Итерировать массив как пару (текущий, следующий) в JavaScript
В вопросе " Итерация списка как пары (текущий, следующий)" в Python OP заинтересован в итерации списка Python как серии current, next
пар. У меня та же проблема, но я хотел бы сделать это в JavaScript самым чистым способом, возможно, с использованием lodash.
Это легко сделать с помощью простого цикла for
, но это не очень элегантно.
for (var i = 0; i < arr.length - 1; i++) {
var currentElement = arr[i];
var nextElement = arr[i + 1];
}
Лодаш почти может сделать это:
_.forEach(_.zip(arr, _.rest(arr)), function(tuple) {
var currentElement = tuple[0];
var nextElement = tuple[1];
})
Тонкая проблема с этим в том, что на последней итерации nextElement
будет undefined
.
Конечно, идеальным решением было бы просто быть pairwise
функция lodash, что только петельные, насколько это необходимо.
_.pairwise(arr, function(current, next) {
// do stuff
});
Существуют ли какие-либо библиотеки, которые уже делают это? Или есть другой хороший способ сделать попарно итерацию в JavaScript, который я не пробовал?
Пояснение: если arr = [1, 2, 3, 4]
, то моя pairwise
функция будет повторяться следующим образом: [1, 2]
, [2, 3]
, [3, 4]
, а не [1, 2]
, [3, 4]
. Это то, о чем спрашивал ОП в исходном вопросе для Python.
Ответы
Ответ 1
Не уверен, зачем вам это нужно, но вы можете просто сделать "уродливую" часть функции, а затем она выглядит хорошо:
arr = [1, 2, 3, 4];
function pairwise(arr, func){
for(var i=0; i < arr.length - 1; i++){
func(arr[i], arr[i + 1])
}
}
pairwise(arr, function(current, next){
console.log(current, next)
})
Вы можете даже слегка изменить его, чтобы иметь возможность выполнять итерацию всех пар i, я + n, а не только следующего:
function pairwise(arr, func, skips){
skips = skips || 1;
for(var i=0; i < arr.length - skips; i++){
func(arr[i], arr[i + skips])
}
}
pairwise([1, 2, 3, 4, 5, 6, 7], function(current,next){
console.log(current, next) // displays (1, 3), (2, 4), (3, 5) , (4, 6), (5, 7)
}, 2)
Ответ 2
В Ruby это называется each_cons
:
(1..5).each_cons(2).to_a # => [[1, 2], [2, 3], [3, 4], [4, 5]]
Это было предложено для Лодаша, но отклонено; тем не менее, есть модуль " каждый минус " на npm:
const eachCons = require('each-cons')
eachCons([1, 2, 3, 4, 5], 2) // [[1, 2], [2, 3], [3, 4], [4, 5]]
Там также aperture
функция Ramda, которая делает то же самое:
const R = require('ramda')
R.aperture(2, [1, 2, 3, 4, 5]) // [[1, 2], [2, 3], [3, 4], [4, 5]]
Ответ 3
Этот ответ вдохновлен ответом, который я видел на аналогичный вопрос, но в Haskell: fooobar.com/questions/575816/...
Мы можем использовать помощники из Lodash, чтобы написать следующее:
const zipAdjacent = function<T> (ts: T[]): [T, T][] {
return zip(dropRight(ts, 1), tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]
(В отличие от эквивалента Haskell, нам нужно dropRight
, потому что Lodash zip
ведет себя по-разному к Haskell's`: он будет использовать длину самого длинного массива вместо кратчайшего.)
То же самое в Рамде:
const zipAdjacent = function<T> (ts: T[]): [T, T][] {
return R.zip(ts, R.tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]
Хотя у Ramda уже есть функция, которая покрывает это имя aperture. Это немного более общий, поскольку он позволяет вам определить, сколько последовательных элементов вы хотите, вместо того, чтобы по умолчанию использовать значение 2:
R.aperture(2, [1,2,3,4]); // => [[1,2], [2,3], [3,4]]
R.aperture(3, [1,2,3,4]); // => [[1,2,3],[2,3,4]]
Ответ 4
Мы можем обернуть Array.reduce немного сделать это и сохранить все в чистоте.
Индексы петли/петли/внешние библиотеки не требуются.
Если требуется результат, просто создайте массив для его сбора.
function pairwiseEach(arr, callback) {
arr.reduce((prev, current) => {
callback(prev, current)
return current
})
}
function pairwise(arr, callback) {
const result = []
arr.reduce((prev, current) => {
result.push(callback(prev, current))
return current
})
return result
}
const arr = [1, 2, 3, 4]
pairwiseEach(arr, (a, b) => console.log(a, b))
const result = pairwise(arr, (a, b) => [a, b])
const output = document.createElement('pre')
output.textContent = JSON.stringify(result)
document.body.appendChild(output)
Ответ 5
Вот простой однострочный:
[1,2,3,4].reduce((acc, v, i, a) => { if (i < a.length - 1) { acc.push([a[i], a[i+1]]) } return acc; }, []).forEach(pair => console.log(pair[0], pair[1]))
Или отформатирован:
[1, 2, 3, 4].
reduce((acc, v, i, a) => {
if (i < a.length - 1) {
acc.push([a[i], a[i + 1]]);
}
return acc;
}, []).
forEach(pair => console.log(pair[0], pair[1]));
который регистрирует:
1 2
2 3
3 4
Ответ 6
Здесь существует общее функциональное решение без каких-либо зависимостей:
const nWise = (n, array) => {
iterators = Array(n).fill()
.map(() => array[Symbol.iterator]());
iterators
.forEach((it, index) => Array(index).fill()
.forEach(() => it.next()));
return Array(array.length - n + 1).fill()
.map(() => (iterators
.map(it => it.next().value);
};
const pairWise = (array) => nWise(2, array);
Я знаю, что это не выглядит хорошо, но, вводя некоторые общие функции утилиты, мы можем сделать его намного приятнее:
const sizedArray = (n) => Array(n).fill();
Я мог бы использовать sizedArray
в сочетании с forEach
для times
реализации, но это было бы неэффективной реализацией. ИМХО это нормально использовать императивный код для такой самоочевидной функции:
const times = (n, cb) => {
while (0 < n--) {
cb();
}
}
Если вам интересны более хардкорные решения, проверьте этот ответ.
К сожалению, Array.fill
принимает только одно значение, а не обратный вызов. Таким образом, Array(n).fill(array[Symbol.iterator]())
будет использовать одно и то же значение в каждой позиции. Мы можем обойти это следующим образом:
const fillWithCb = (n, cb) => sizedArray(n).map(cb);
Окончательная реализация:
const nWise = (n, array) => {
iterators = fillWithCb(n, () => array[Symbol.iterator]());
iterators.forEach((it, index) => times(index, () => it.next()));
return fillWithCb(
array.length - n + 1,
() => (iterators.map(it => it.next().value),
);
};
Изменив стиль параметра на currying, определение попарно будет выглядеть намного приятнее:
const nWise = n => array => {
iterators = fillWithCb(n, () => array[Symbol.iterator]());
iterators.forEach((it, index) => times(index, () => it.next()));
return fillWithCb(
array.length - n + 1,
() => iterators.map(it => it.next().value),
);
};
const pairWise = nWise(2);
И если вы запустите это, вы получите:
> pairWise([1, 2, 3, 4, 5]);
// [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
Ответ 7
Здесь мой подход, используя Array.prototype.shift
:
Array.prototype.pairwise = function (callback) {
const copy = [].concat(this);
let next, current;
while (copy.length) {
current = next ? next : copy.shift();
next = copy.shift();
callback(current, next);
}
};
Это можно вызвать следующим образом:
// output:
1 2
2 3
3 4
4 5
5 6
[1, 2, 3, 4, 5, 6].pairwise(function (current, next) {
console.log(current, next);
});
Итак, чтобы разбить его:
while (this.length) {
Array.prototype.shift
непосредственно мутирует массив, поэтому, когда элементы не оставлены, длина, очевидно, решит 0
. Это значение "ложно" в JavaScript, поэтому цикл будет прерываться.
current = next ? next : this.shift();
Если параметр next
был установлен ранее, используйте его как значение current
. Это позволяет использовать одну итерацию для каждого элемента, чтобы все элементы можно было сравнить с их соседним преемником.
Остальное просто.
Ответ 8
d3.js предоставляет встроенную версию того, что в определенных языках называется sliding
:
console.log(d3.pairs([1, 2, 3, 4])); // [[1, 2], [2, 3], [3, 4]]
<script src="http://d3js.org/d3.v5.min.js"></script>
Ответ 9
Другое решение с использованием итерируемых и генераторных функций:
function * pairwise (iterable) {
const iterator = iterable[Symbol.iterator]()
let current = iterator.next()
let next = iterator.next()
while (!current.done) {
yield [current.value, next.value]
current = next
next = iterator.next()
}
}
console.log(...pairwise([]))
console.log(...pairwise(['apple']))
console.log(...pairwise(['apple', 'orange', 'kiwi', 'banana']))
console.log(...pairwise(new Set(['apple', 'orange', 'kiwi', 'banana'])))
Ответ 10
Мои два цента. Базовая нарезка, генераторная версия.
function* generate_windows(array, window_size) {
const max_base_index = array.length - window_size;
for(let base_index = 0; base_index <= max_base_index; ++base_index) {
yield array.slice(base_index, base_index + window_size);
}
}
const windows = generate_windows([1, 2, 3, 4, 5, 6, 7, 8, 9], 3);
for(const window of windows) {
console.log(window);
}
Ответ 11
Для этого просто используйте forEach со всеми его параметрами:
yourArray.forEach((current, idx, self) => {
if (let next = self[idx + 1]) {
//your code here
}
})
Ответ 12
У Lodash есть метод, который позволяет вам сделать это: https://lodash.com/docs#chunk
_.chunk(array, 2).forEach(function(pair) {
var first = pair[0];
var next = pair[1];
console.log(first, next)
})
Ответ 13
Возможно, вы хотите что-то вроде этого:
Array.prototype.pairEach = function(callback){
for(var i=0, l= this.length-1;i<l;i++){
callback(this[i], this[i+1]);
}
}
//call as follows
var a = [1,2,3,4];
a.pairEach(callback);