在 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();
我可以将 sizedArray
与 forEach
结合用于 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]));
问题中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();
我可以将 sizedArray
与 forEach
结合用于 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]));