Iterate an array as a pair (current, next) in JavaScript

21,900

Solution 1

Just make the "ugly" part into a function and then it looks nice:

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)
})

You can even slightly modify it to be able to make iterate all i, i+n pairs, not just the next one:

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)

Solution 2

In Ruby, this is called each_cons (each consecutive):

(1..5).each_cons(2).to_a # => [[1, 2], [2, 3], [3, 4], [4, 5]]

It was proposed for Lodash, but rejected; however, there's an each-cons module on npm:

const eachCons = require('each-cons')

eachCons([1, 2, 3, 4, 5], 2) // [[1, 2], [2, 3], [3, 4], [4, 5]]

There's also an aperture function in Ramda which does the same thing:

const R = require('ramda')

R.aperture(2, [1, 2, 3, 4, 5]) // [[1, 2], [2, 3], [3, 4], [4, 5]]

Solution 3

This answer is inspired by an answer I saw to a similar question but in Haskell: https://stackoverflow.com/a/4506000/5932012

We can use helpers from Lodash to write the following:

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]]

(Unlike the Haskell equivalent, we need dropRight because Lodash's zip behaves differently to Haskell's`: it will use the length of the longest array instead of the shortest.)

The same in Ramda:

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]]

Although Ramda already has a function that covers this called aperture. This is slightly more generic because it allows you to define how many consecutive elements you want, instead of defaulting to 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]]

Solution 4

Another solution using iterables and generator functions:

function * pairwise (iterable) {
    const iterator = iterable[Symbol.iterator]()
    let current = iterator.next()
    let next = iterator.next()
    while (!next.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'])))

Advantages:

  • Works on all iterables, not only arrays (eg. Sets).
  • Does not create any intermediate or temporary array.
  • Lazy evaluated, works efficiently on very large iterables.

Typescript version:

function* pairwise<T>(iterable:Iterable<T>) : Generator<Array<T>> {
    const iterator = iterable[Symbol.iterator]();
    let current = iterator.next();
    let next = iterator.next();
    while (!next.done) {
        yield [current.value, next.value];
        current = next;
        next = iterator.next();
    }
}

Solution 5

Here's a generic functional solution without any dependencies:

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);

I know doesn't look nice at all but by introducing some generic utility functions we can make it look a lot nicer:

const sizedArray = (n) => Array(n).fill();

I could use sizedArray combined with forEach for times implementation, but that'd be an inefficient implementation. IMHO it's ok to use imperative code for such a self-explanatory function:

const times = (n, cb) => {
  while (0 < n--) {
    cb();
  }
}

If you're interested in more hardcore solutions, please check this answer.

Unfortunately Array.fill only accepts a single value, not a callback. So Array(n).fill(array[Symbol.iterator]()) would put the same value in every position. We can get around this the following way:

const fillWithCb = (n, cb) => sizedArray(n).map(cb);

The final implementation:

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),
  );
};

By changing the parameter style to currying, the definition of pairwise would look a lot nicer:

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);

And if you run this you get:

> pairWise([1, 2, 3, 4, 5]);
// [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
Share:
21,900
therealrootuser
Author by

therealrootuser

Updated on July 24, 2021

Comments

  • therealrootuser
    therealrootuser almost 3 years

    In the question Iterate a list as pair (current, next) in Python, the OP is interested in iterating a Python list as a series of current, next pairs. I have the same problem, but I'd like to do it in JavaScript in the cleanest way possible, perhaps using lodash.

    It is easy to do this with a simple for loop, but it doesn't feel very elegant.

    for (var i = 0; i < arr.length - 1; i++) {
      var currentElement = arr[i];
      var nextElement = arr[i + 1];
    }
    

    Lodash almost can do this:

    _.forEach(_.zip(arr, _.rest(arr)), function(tuple) {
      var currentElement = tuple[0];
      var nextElement = tuple[1];
    })
    

    The subtle problem with this that on the last iteration, nextElement will be undefined.

    Of course the ideal solution would simply be a pairwise lodash function that only looped as far as necessary.

    _.pairwise(arr, function(current, next) {
      // do stuff 
    });
    

    Are there any existing libraries that do this already? Or is there another nice way to do pairwise iteration in JavaScript that I haven't tried?


    Clarification: If arr = [1, 2, 3, 4], then my pairwise function would iterate as follows: [1, 2], [2, 3], [3, 4], not [1, 2], [3, 4]. This is what the OP was asking about in the original question for Python.