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

2019-01-24 13:48发布

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.

9条回答
Evening l夕情丶
2楼-- · 2019-01-24 14:05

Here's a simple one-liner:

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

Or formatted:

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

which logs:

1 2
2 3
3 4
查看更多
SAY GOODBYE
3楼-- · 2019-01-24 14:06

In Ruby, this is called each_cons:

(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:

var 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:

var R = require('ramda')

R.aperture(2, [1, 2, 3, 4, 5]) // [[1, 2], [2, 3], [3, 4], [4, 5]]
查看更多
兄弟一词,经得起流年.
4楼-- · 2019-01-24 14:07

Here's my approach, using 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);
    }
};

This can be invoked as follows:

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

So to break it down:

while (this.length) {

Array.prototype.shift directly mutates the array, so when no elements are left, length will obviously resolve to 0. This is a "falsy" value in JavaScript, so the loop will break.

current = next ? next : this.shift();

If next has been set previously, use this as the value of current. This allows for one iteration per item so that all elements can be compared against their adjacent successor.

The rest is straightforward.

查看更多
等我变得足够好
5楼-- · 2019-01-24 14:09

Lodash does have a method that allows you to do this: https://lodash.com/docs#chunk

_.chunk(array, 2).forEach(function(pair) {
  var first = pair[0];
  var next = pair[1];
  console.log(first, next)
})
查看更多
时光不老,我们不散
6楼-- · 2019-01-24 14:14

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 ] ]
查看更多
何必那么认真
7楼-- · 2019-01-24 14:22

d3.js provides a built-in version of what is called in certain languages a 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>

# d3.pairs(array[, reducer]) <>

For each adjacent pair of elements in the specified array, in order, invokes the specified reducer function passing the element i and element i - 1. If a reducer is not specified, it defaults to a function which creates a two-element array for each pair.

查看更多
登录 后发表回答