【问题标题】:Using map() on an iterator在迭代器上使用 map()
【发布时间】:2017-10-08 16:22:49
【问题描述】:

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

但是我不能在那个迭代器上使用forEach()map() 并且在那个迭代器上实现一个while 循环似乎是一种反模式,因为ES6 提供了像map() 这样的函数。

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

【问题讨论】:

  • 不是开箱即用的,但您可以使用第三方库,如 lodash map 函数也支持地图。
  • Map 本身有一个 forEach 用于迭代其键值对。
  • 将迭代器转换为数组并像Array.from(m.values()).map(...) 一样映射它,但我认为这不是最好的方法。
  • 哪个问题适合您使用迭代器来解决,而数组更适合使用Array#map
  • @NinaScholz 我正在使用像这里这样的通用集合:stackoverflow.com/a/29783624/4279201

标签: javascript dictionary syntax ecmascript-6 iterator


【解决方案1】:

最简单性能最低的方法是:

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

更好

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

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

正如@hraban 在 cmets 中指出的那样,使用 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 对于迭代器:是的。迭代器上的 .map 可以被认为是生成器上的转换,它返回一个迭代器本身。弹出一个元素调用底层迭代器,转换元素,然后返回。
  • 这个答案的问题在于它将可能是 O(1) 的内存算法变成了 O(n) 的内存算法,这对于较大的数据集来说是相当严重的。当然,除了需要有限的、不可流式的迭代器。问题的标题是“在迭代器上使用 map()”,我不同意惰性和无限序列不是问题的一部分。这正是人们使用迭代器的方式。 “地图”只是一个例子(“Say..”)。这个答案的好处是它的简单性,这一点非常重要。
  • @hraban 感谢您加入此讨论。我可以更新答案以包含一些注意事项,以便未来的旅行者可以了解信息的前沿和中心。归根结底,我们经常不得不在简单和最佳性能之间做出决定。我通常会支持更简单(调试、维护、解释)而不是性能。
  • @ktilcu 您可以改为调用Array.from(m, ([key,value]) => /* whatever */)(注意映射函数在from 内),然后不创建中间数组(source)。它仍然从 O(1) 移动到 O(n),但至少迭代和映射只发生在一次完整的迭代中。
【解决方案2】:

你可以定义另一个迭代器函数来循环这个:

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

function* mapIterator(iterator, mapping) {
    for (let i of iterator) {
        yield mapping(i);
    }
}

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 应该绰绰有余。

【讨论】:

  • 有限的内存怎么能容纳无限的数据结构?
  • 它没有,这就是重点。使用它,您可以通过将迭代器源链接到一堆迭代器转换,最后链接到消费者接收器来创建“数据流”。例如。用于流式音频处理、处理大文件、数据库聚合器等。
  • 我喜欢这个答案。谁能推荐一个在可迭代对象上提供类似数组的方法的库?
  • mapIterator() 不保证底层迭代器将被正确关闭(iterator.return() 调用),除非返回值的 next 至少被调用一次。见:repeater.js.org/docs/safety
  • 你为什么手动使用迭代器协议而不是for .. of .. loop
【解决方案3】:

您可以使用 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 做对了:doc.rust-lang.org/std/iter/trait.Iterator.html
  • “一如既往,Rust 做对了”当然...对于迭代器接口 github.com/tc39/proposal-iterator-helpers 的所有类型的辅助函数有一个标准化建议,您现在可以通过导入 @987654325 将它与 corejs 一起使用@fn from "core-js-pure/features/iterator" 返回“新”迭代器。
【解决方案4】:

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

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

【讨论】:

  • 这在打字稿中看起来如何?
【解决方案5】:

这种最简单且最高效的方法是使用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(...) 的单独调用,这将迭代可迭代两次并且性能更差。

【讨论】:

    【解决方案6】:

    看看https://www.npmjs.com/package/fluent-iterable

    适用于所有可迭代对象(映射、生成器函数、数组)和异步可迭代对象。

    const map = new Map();
    ...
    console.log(fluent(map).filter(..).map(..));
    

    【讨论】:

      【解决方案7】:

      这里的其他答案是……很奇怪。他们似乎正在重新实现部分迭代协议。你可以这样做:

      function* mapIter(iterable, callback) {
        for (let x of iterable) {
          yield callback(x);
        }
      }
      

      如果你想要一个具体的结果,只需使用扩展运算符...

      [...mapIter([1, 2, 3], x => x**2)]
      

      【讨论】:

        【解决方案8】:

        有一个提案,为Iterator 带来了多个辅助函数:https://github.com/tc39/proposal-iterator-helpers (rendered)

        您现在可以通过core-js-pure 使用它:

        import { from as iterFrom } from "core-js-pure/features/iterator";
        
        // or if it's working for you (it should work according to the docs,
        // but hasn't for me for some reason):
        // import iterFrom from "core-js-pure/features/iterator/from";
        
        let m = new Map();
        
        m.set("13", 37);
        m.set("42", 42);
        
        const arr = iterFrom(m.values())
          .map((val) => val * 2)
          .toArray();
        
        // prints "[74, 84]"
        console.log(arr);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-01-19
          • 1970-01-01
          • 1970-01-01
          • 2012-04-19
          • 1970-01-01
          • 2019-05-30
          • 1970-01-01
          • 2014-05-27
          相关资源
          最近更新 更多