JavaScript中类似于Ruby的each_cons方法的等效方法是什么?

3

这个问题已经被很多语言问过,但还没有被JavaScript问过。

Ruby有一个名为Enumerable#each_cons的方法,看起来像这样:

puts (0..5).each_cons(2).to_a
# [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]]
puts (0..5).each_cons(3).to_a
# [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

在JavaScript中,我如何使用Array来实现类似的方法?

3个回答

7

以下是一个可以实现此功能的函数(ES6+):

// functional approach
const eachCons = (array, num) => {
    return Array.from({ length: array.length - num + 1 },
                      (_, i) => array.slice(i, i + num))
}

// prototype overriding approach
Array.prototype.eachCons = function(num) {
  return Array.from({ length: this.length - num + 1 },
                    (_, i) => this.slice(i, i + num))
}


const array = [0,1,2,3,4,5]
const log = data => console.log(JSON.stringify(data))

log(eachCons(array, 2))
log(eachCons(array, 3))

log(array.eachCons(2))
log(array.eachCons(3))

你需要猜测结果数组的长度(n = length - num + 1),然后你可以利用JavaScript的array.slice函数获取所需的块,迭代n次。


2
使用内部映射器参数的Array.from的替代语法为Array.from({length:array.length - num + 1}, (_, i) => array.slice(i, i + num)) - charlietfl
1
@UlysseBN,如果你没有注意到的话,你完全可以排除你的map并使用内部映射器本身。与你现在混合在一起的解决方案相比,这肯定会有性能提升。 - claasic
1
@assoron - 已编辑。我很高兴发表了这个问答,每一分钟它都变得更好。 - Ulysse BN

3
你可以获取数组长度,创建一个新的数组,并使用 Array.from 和内置映射器来映射切片后的数组。

Number.prototype[Symbol.iterator] = function* () {
    for (var i = 0; i < this; i++) yield i;
};

Array.prototype.eachCons = function (n) {
    return Array.from({ length: this.length - n + 1}, (_, i) => this.slice(i, i + n));
}

console.log([...10].eachCons(2));
console.log([...10].eachCons(3));
.as-console-wrapper { max-height: 100% !important; top: 0; }


1
Number 上的 [Symbol.iterator] 绝对是一个有趣的方法,我不知道它实际上会起作用。 - briosheje
(仅供参考,这里应该是“<= this”在Number迭代器中,因为(0..5)实际上是[0,1,2,3,4,5]。) - briosheje

2
这里有一个单行解决方案,依赖于函数生成器。也许略有不同,但仍值得一试。
我还添加了原型,不确定为什么要这样做,但仍然...
此解决方案不需要ES6,也可以与ES5一起使用,只需注意IE根本不支持函数生成器。

function* eachCons(arr, num) {
  for (let counter = 0; counter <= arr.length - num; counter++) yield arr.slice(counter, counter + num);
}

Array.prototype.eachCons = function* (num) {
  for (let counter = 0; counter <= this.length - num; counter++) yield this.slice(counter, counter + num);
}

const array = [0,1,2,3,4,5];
const log = data => console.log(JSON.stringify(data))
log([...eachCons(array, 2)]);
log([...eachCons(array, 3)]);

// Iterable way
for (let [...pack] of eachCons(array, 2)) {
  console.log('pack is', pack);
}
// Prototype...
for (let [...pack] of array.eachCons(2)) {
  console.log('pack is', pack);
}


我非常喜欢这个解决方案!然而,根据情况,我更愿意使用返回数组的解决方案。例如,生成器解决方案不允许eachCons([1,2,3,3], 3).map((a) => a[0] + a[1])。但是,对于该解决方案的多功能性和巧妙性给出+1。我不会使用原型解决方案,但我知道有些人会使用…… - Ulysse BN
@UlysseBN 嗯,你可以使用:Array.from 但是你说得对,这是生成器的“缺点” :P - briosheje
1
@UlysseBN 顺便提一下,我刚刚注意到 to_a 在 Ruby 中被调用,因此它很可能在 Ruby 中也返回一个迭代器,但我不是很确定。 - briosheje
@briosheje 在 Ruby 中返回一个 Enumerator。它可以访问整个 Enumerable 接口。所以不是 JS 中的 Iterator,但也不是 Array。我想这真的取决于情况。 - Ulysse BN
@UlysseBN 我认为 Ruby 的 Enumerator 和 JavaScript 的迭代器非常相似,就我所知它们应该是相当相似的,而且它们应该与 Python 的迭代器和生成器类似。 - briosheje
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接