Using map() on an iterator

JavascriptDictionarySyntaxEcmascript 6Iterator

Javascript Problem Overview


Say we have a Map: let m = new Map();, using m.values() returns a map iterator.

But I can't use forEach() or map() on that iterator and implementing a while loop on that iterator seems like an anti-pattern since ES6 offer functions like map().

So is there a way to use map() on an iterator?

Javascript Solutions


Solution 1 - Javascript

The simplest and least performant way to do this is:

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

Better yet

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

Array.from takes any iterable or array-like thing and converts it into an array! As Daniel points out in the comments, we can add a mapping function to the conversion to remove an iteration and subsequently an intermediate array.

Using Array.from will move your performance from O(1) to O(n) as @hraban points out in the comments. Since m is a Map, and they can't be infinite, we don't have to worry about an infinite sequence. For most instances, this will suffice.

There are a couple of other ways to loop through a map.

Using forEach

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

Using 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

Solution 2 - Javascript

You could define another iterator function to loop over this:

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(','));

Now you might ask: why not just use Array.from instead? Because this will run through the entire iterator, save it to a (temporary) array, iterate it again and then do the mapping. If the list is huge (or even potentially infinite) this will lead to unnecessary memory usage.

Of course, if the list of items is fairly small, using Array.from should be more than sufficient.

Solution 3 - Javascript

This simplest and most performant way is to use the second argument to Array.from to achieve this:

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

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

This approach works for any non-infinite iterable. And it avoids having to use a separate call to Array.from(map).map(...) which would iterate through the iterable twice and be worse for performance.

Solution 4 - Javascript

Other answers here are... Weird. They seem to be re-implementing parts of the iteration protocol. You can just do this:

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

and if you want a concrete result just use the spread operator ....

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

Solution 5 - Javascript

There is a proposal, that brings multiple helper functions to Iterator: <https://github.com/tc39/proposal-iterator-helpers> (rendered)

You can use it today by utilizing 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);

Solution 6 - Javascript

You could retrieve an iterator over the iterable, then return another iterator that calls the mapping callback function on each iterated element.

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

Solution 7 - Javascript

Take a look at https://www.npmjs.com/package/fluent-iterable

Works with all of iterables (Map, generator function, array) and async iterables.

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

Solution 8 - Javascript

You could use itiriri that implements array-like methods for iterables:

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

Solution 9 - Javascript

Based on the answer from MartyO256 (https://stackoverflow.com/a/53159921/7895659), a refactored typescript approach could be the following one:

function mapIterator<TIn, TOut>(
  iterator: Iterator<TIn>,
  callback: (input: TIn) => TOut,
): Iterator<TOut> {
  return {
    next() {
      const result: IteratorResult<TIn> = iterator.next();

      if (result.done === true) {
        return result;
      } else {
        return {
          done: false,
          value: callback(result.value),
        };
      }
    },
  };
}

export function mapIterable<TIn, TOut>(
  iterable: Iterable<TIn>,
  callback: (input: TIn) => TOut,
): Iterable<TOut> {
  const iterator: Iterator<TIn> = iterable[Symbol.iterator]();
  const mappedIterator: Iterator<TOut> = mapIterator(iterator, callback);

  return {
    [Symbol.iterator]: () => mappedIterator,
  };
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionshinzouView Question on Stackoverflow
Solution 1 - JavascriptktilcuView Answer on Stackoverflow
Solution 2 - JavascriptsheeanView Answer on Stackoverflow
Solution 3 - JavascriptIan Storm TaylorView Answer on Stackoverflow
Solution 4 - JavascriptcowlicksView Answer on Stackoverflow
Solution 5 - JavascriptchpioView Answer on Stackoverflow
Solution 6 - JavascriptMartyO256View Answer on Stackoverflow
Solution 7 - JavascriptkataikView Answer on Stackoverflow
Solution 8 - JavascriptdimadeveatiiView Answer on Stackoverflow
Solution 9 - JavascriptRoberto Pintos LópezView Answer on Stackoverflow