It seems there's no good standard library way of doing something like this?
let thismap = new Map([[1,2],[2,3]])
console.log(thismap.entries().reduce((prev, [a,b])=>prev a * b, 0))
Uncaught TypeError: thismap.entries(...).reduce is not a function
I assume this is due to the entries() function returning an iterator? I don't want to Array.from(thismap.entries()).reduce(...)
, as that would unnecessarily build the array in memory. It feels like I'm missing something, but I also don't want to reimplement something that should be in the standard library.
I suppose if I was using an object instead (not a satisfactory solution here for other reasons), the entries() would essentially be an array expansion instead of an iterator (although I suppose it could be implemented with memory efficiency in mind). But still, I'd want to know how to reduce an iterator
CodePudding user response:
You can use a for of
loop and handle the summing manually. This uses the iterator without creating a temporary array. Note that here we don't even have to call entries
manually because Map.prototype[Symbol.iterator] === Map.prototype.entries
.
const map = new Map([[1, 2], [2, 3]])
let sum = 0
for (const [a, b] of map) sum = a * b
console.log(sum)
You can of course also factor this out into a utility function in case you need it more often. Here I created a function lazyReduce
that works like Array.prototype.reduce
but operates on iterables of any kind:
function lazyReduce (originalIterable, callback, initialValue) {
let i = 0
let accumulator = initialValue
let iterable = originalIterable
// This part exists to implement the behavior of reduce without initial value
// in the same way Array.prototype.reduce does it
if (arguments.length < 3) {
iterable = iterable[Symbol.iterator]()
const { value: accumulator, done } = iterable.next()
if (done) throw new TypeError('Reduce of empty iterable with no initial value')
i
}
for (const element of iterable) {
accumulator = callback(accumulator, element, i , originalIterable)
}
return accumulator
}
const map = new Map([[1, 2], [2, 3]])
console.log(lazyReduce(map, (prev, [a, b]) => prev a * b, 0))
If you wanted, you could extend the prototypes of Map
, Set
, etc., i.e. Map.prototype.reduce = function (...args) { return lazyReduce(this, ...args) }
. (Note: Some other things that return iterators would be harder to extend, but still possible. For example RegExpStringIterator
which doesn't exist as global variable, but you can still do Object.getPrototypeOf(''.matchAll(/./g)).reduce = ...
. Similar ideas would work for Generator
.)
CodePudding user response:
I also don't want to reimplement something that should be in the standard library.
It really should indeed. There's a proposal to add it: Iterator Helpers. While waiting for it, you can already use the polyfill which will make your original code work :-)