Iterate an array as a pair (current, next) in JavaScript
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 ] ]
therealrootuser
Updated on July 24, 2021Comments
-
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 beundefined
.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 mypairwise
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.