在迭代器上使用map()


88

假设我们有一个Map :,let m = new Map();使用会m.values()返回一个地图迭代器。

但我不能在迭代器上使用forEach()map(),并且在该迭代器上实现while循环似乎是一种反模式,因为ES6提供了的功能map()

那么有没有办法map()在迭代器上使用?


不是开箱即用的,但是您可以使用第三方库,例如lodash map也支持Map的函数。
危险

Map本身具有forEach来迭代其键值对。
危险

将迭代器转换为数组,然后像在其上进行映射一样Array.from(m.values()).map(...),但是我认为这不是最好的方法。
JiminP

您喜欢使用迭代器解决哪个问题,而数组更适合使用Array#map呢?
妮娜·斯科尔斯

1
@NinaScholz我使用的是这样的通用集:stackoverflow.com/a/29783624/4279201
shinzou

Answers:


81

最简单最高性能做到这一点的方法是:

Array.from(m).map(([key,value]) => /* whatever */)

更好了

Array.from(m, ([key, value]) => /* whatever */))

Array.from接受任何可迭代的或类似数组的东西,并将其转换为数组!正如Daniel在评论中指出的,我们可以在转换中添加一个映射函数,以删除迭代,然后删除中间数组。

@hraban在评论中指出,使用Array.from会将您的表现从O(1)移到O(n)。由于mMap,并且它们不能是无限的,所以我们不必担心无限的序列。在大多数情况下,这就足够了。

还有其他几种方法可以遍历地图。

使用 forEach

m.forEach((value,key) => /* stuff */ )

使用 for..of

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
  console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one

地图可以无限长吗?
ktilcu

2
@ktilcu的迭代器:是的。可以将迭代器上的.map视为生成器上的转换,该转换本身返回迭代器。弹出一个元素将调用基础迭代器,将其转换并返回。
hraban

7
这个答案的问题是将可能是O(1)的存储算法转换为O(n)的算法,这对于较大的数据集而言非常严重。当然,除了需要有限的,不可流式的迭代器之外。问题的标题是“在迭代器上使用map()”,我不同意惰性和无限序列不是问题的一部分。这正是人们使用迭代器的方式。“地图”仅是示例(“说..”)。这个答案的好处是它的简单性,这一点非常重要。
hraban

1
@hraban感谢您加入讨论。我可以更新答案以包含一些警告,以便将来的旅行者掌握信息的前沿和中心。归根结底,我们常常不得不在简单性能和最佳性能之间做出决定。在性能方面,我通常会更简单(调试,维护,解释)。
ktilcu

3
@ktilcu您可以改为调用Array.from(m, ([key,value]) => /* whatever */)(注意映射函数位于中from),然后不创建任何中间数组(source)。它仍然从O(1)移到O(n),但是至少迭代和映射仅发生在一个完整的迭代中。
丹尼尔(Daniel)

18

您可以定义另一个迭代器函数来对此进行循环:

function* generator() {
    for(let i = 0; i < 10; i++) {
        console.log(i);
        yield i;
    }
}

function* mapIterator(iterator, mapping) {
    while (true) {
        let result = iterator.next();
        if (result.done) {
            break;
        }
        yield mapping(result.value);
    }
}

let values = generator();
let mapped = mapIterator(values, (i) => {
    let result = i*2;
    console.log(`x2 = ${result}`);
    return result;
});

console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));

现在您可能会问:为什么不只使用它Array.from呢?因为这将贯穿整个迭代器,所以将其保存到(临时)数组中,再次对其进行迭代,然后进行映射。如果列表很大(甚至可能是无限的),这将导致不必要的内存使用。

当然,如果项目列表很小,那么使用Array.from应该绰绰有余。


有限的内存如何容纳无限的数据结构?
shinzou

3
并非如此,这就是重点。使用此方法,您可以通过将迭代器源链接到一堆迭代器转换以及最终的使用者接收器来创建“数据流”。例如,用于流音频处理,处理大文件,数据库上的聚合器等
hraban

1
我喜欢这个答案。谁能推荐一个在可迭代对象上提供类似数组方法的库?
乔尔·马龙

1
mapIterator()iterator.return()除非返回值的下一个至少被调用一次,否则不能保证基础迭代器将被正确关闭(调用)。参见:repeater.js.org/docs/safety
JakaJančar

11

这种最简单,最高效的方法是使用第二个参数Array.from来实现此目的:

const map = new Map()
map.set('a', 1)
map.set('b', 2)

Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']

这种方法适用于任何非无限迭代。而且它避免了必须使用单独的调用,Array.from(map).map(...)该调用将迭代可迭代两次,从而降低性能。


3

您可以在可迭代对象上检索一个迭代器,然后返回另一个迭代器,该迭代器在每个迭代元素上调用映射回调函数。

const map = (iterable, callback) => {
  return {
    [Symbol.iterator]() {
      const iterator = iterable[Symbol.iterator]();
      return {
        next() {
          const r = iterator.next();
          if (r.done)
            return r;
          else {
            return {
              value: callback(r.value),
              done: false,
            };
          }
        }
      }
    }
  }
};

// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8

2

您可以使用为可迭代对象实现类似于数组的方法的itiriri

import { query } from 'itiriri';

let m = new Map();
// set map ...

query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();

真好!这就是应该完成JS API的方式。与往常一样,Rust正确地做到了
飞羊

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.