在 JavaScript 中将数组作为一对 (current, next) 进行迭代

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

问题中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.

用一个简单的for循环很容易做到这一点,但感觉不是很优雅。

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

Lodash 几乎可以做到这一点:

_.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].这就是 OP 在 the original question for Python.

中询问的内容

Lodash 确实有一个方法可以让你这样做:https://lodash.com/docs#chunk

_.chunk(array, 2).forEach(function(pair) {
  var first = pair[0];
  var next = pair[1];
  console.log(first, next)
})

把"ugly"部分做成一个函数就好了:

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

这是我的方法,使用 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 中的 "falsy" 值,因此循环将中断。

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

如果之前设置了next,则将其作为current的值。这允许每个项目进行一次迭代,以便可以将所有元素与其相邻的后继元素进行比较。

剩下的就简单了。

在Ruby中,这叫做each_cons(每个连续):

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

它是 npm 上的 proposed for Lodash, but rejected; however, there's an each-cons 模块:

const eachCons = require('each-cons')

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

还有一个 aperture function in Ramda 做同样的事情:

const R = require('ramda')

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

我们可以包裹 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)

这是一个简单的单行代码:

[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

这个答案的灵感来自于我在 Haskell 中看到的类似问题的答案:

我们可以使用 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 的行为不同:它将使用最长数组的长度而不是最短的。)

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

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

这是一个没有任何依赖关系的通用功能解决方案:

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

我可以将 sizedArrayforEach 结合用于 times 实施,但这是一个低效的实施。恕我直言,可以为这样的 self-explanatory 函数使用命令式代码:

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,pairwise 的定义看起来会更好:

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

d3.js provides a built-in 某些语言中所谓的版本 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.

另一种使用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'])))

优点:

  • 适用于所有可迭代对象,而不仅仅是数组(例如集合)。
  • 不创建任何中间或临时数组。
  • 懒惰求值,在非常大的可迭代对象上高效工作。

打字稿版本:

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

我的两分钱。基本切片,生成器版本。

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

只需使用 forEach 及其所有参数即可:

yourArray.forEach((current, idx, self) => {
  if (let next = self[idx + 1]) {
    //your code here
  }
})

希望对大家有所帮助! (和喜欢)

arr = [1, 2, 3, 4];
output = [];
arr.forEach((val, index) => {
  if (index < (arr.length - 1) && (index % 2) === 0) {
    output.push([val, arr[index + 1]])
  }
})

console.log(output);

修改后的 zip:

const pairWise = a => a.slice(1).map((k,i) => [a[i], k]);

console.log(pairWise([1,2,3,4,5,6]));

输出:

[ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ], [ 5, 6 ] ]

通用版本为:

const nWise = n => a => a.slice(n).map((_,i) => a.slice(i, n+i));

console.log(nWise(3)([1,2,3,4,5,6,7,8]));