在JavaScript

时间:2015-08-12 18:55:25

标签: javascript iteration lodash

在问题Iterate a list as pair (current, next) in Python中,OP有兴趣将Python列表迭代为一系列current, next对。我有同样的问题,但我希望尽可能以最干净的方式在JavaScript中使用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中提出的要求。

14 个答案:

答案 0 :(得分:7)

不确定为什么要这样,但你可以做出“丑陋的”#34;部分函数,​​然后它看起来不错:

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)

答案 1 :(得分:6)

在Ruby中,这称为each_cons

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

那是proposed for Lodash,但被拒绝了;但是,npm上有一个each-cons模块:

const eachCons = require('each-cons')

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

aperture中还有一个Ramda函数可以执行相同的操作:

const R = require('ramda')

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

答案 2 :(得分:3)

这个答案的灵感来自于我在Haskell中看到类似问题的答案:https://stackoverflow.com/a/4506000/5932012

我们可以使用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]]

答案 3 :(得分:2)

我们可以稍微包装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)

答案 4 :(得分:2)

这是一个简单的单行:

[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

答案 5 :(得分:2)

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

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来实现,但这样做效率很低。恕我直言,可以使用命令式代码来实现这样一个不言自明的功能:

const times = (n, cb) => {
  while (0 < n--) {
    cb();
  }
}

如果您对更多核心解决方案感兴趣,请查看this answer。

不幸的是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 ] ]

答案 6 :(得分:1)

这是我的方法,使用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。这是一个&#34; falsy&#34; JavaScript中的值,因此循环将会中断。

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

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

其余的很简单。

答案 7 :(得分:1)

使用iterablesgenerator functions的另一种解决方案:

function * pairwise (iterable) {
    const iterator = iterable[Symbol.iterator]()
    let current = iterator.next()
    let next = iterator.next()
    while (!current.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'])))

优势:

  • 不仅可用于数组(例如,集合),还可用于所有可迭代对象。
  • 不创建任何中间或临时数组。
  • 经过懒惰评估,可以在非常大的可迭代对象上有效地工作。

答案 8 :(得分:0)

d3.js提供了built-in版本的某些语言称为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])<>

     

对于指定数组中的每对相邻元素,依次调用传递给元素i和元素i-1的指定化简函数。如果未指定化简,则默认为创建两个元素的函数每对数组。

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

为此只需使用forEach及其所有参数:

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

答案 11 :(得分:0)

希望它可以帮助某人! (和喜欢)

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

答案 12 :(得分:0)

修改后的 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]));

答案 13 :(得分:-1)

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

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