Итерировать массив как пару (текущий, следующий) в 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);