How to filter a JavaScript Map?

JavascriptEcmascript 6

Javascript Problem Overview


Given an ES6 Map and predicate function, how do I safely delete all non-matching elements for the map?


I could not find an official API function, but I can think of two implementations. The first does not attempt to delete in-place, but instead creates a copy:

// version 1:
function filter(map, pred) {
  const result = new Map();
  for (let [k, v] of map) {
    if (pred(k,v)) {
      result.set(k, v);
    }
  }
  return result;
}

const map = new Map().set(1,"one").set(2,"two").set(3,"three");
const even = filter(map, (k,v) => k % 2 === 0);
console.log([...even]); // Output: "[ [ 2, 'two' ] ]"

The other deletes in-place. In my tests, it works but I did not find a guarantee that modifying a map does not break the iterator (of the for-of loop):

// version 2:
function deleteIfNot(map, pred) {
  for (let [k, v] of map) {
    if (!pred(k,v)) {
      map.delete(k);
    }
  }
  return map;
}

const map = new Map().set(1,"one").set(2,"two").set(3,"three");
deleteIfNot(map, (k,v) => k % 2 === 0);
console.log([...map]); // Output: "[ [ 2, 'two' ] ]"

Question:

  • Is the second version (the in-place delete) correct on all platforms?

  • Is there a better way to implement an in-place filter? Maybe some official API that I missed?

Javascript Solutions


Solution 1 - Javascript

If we want to use .filter() iterator, we can apply a simple trick, because there is no .filter operator for ES6 Maps.The approach from Dr. Axel Rauschmayer is:

  • Convert the map into an array of [key,value] pairs.
  • Map or filter the array.
  • Convert the result back to a map.

Example:

const map0 = new Map([ ['a', 1], ['b', 2], ['c', 3] ]);

const map1 = new Map(
  [...map0]
  .filter(([k, v]) => v < 3 )
);

console.info([...map1]); 
//[0: ["a", 1], 1: ["b", 2]]

Solution 2 - Javascript

ES6 iterables have no problems when an entry is deleted inside a loop.

There is no special API that would allow to efficiently filter ES6 map entries without iterating over them.

If a map doesn't have to be immutable and should be modified in-place, creating a new map on filtering provides overhead.

There is also Map forEach, but it presumes that value will be used, too.

Since the map is being filtered only by its key, there's no use for entry object. It can be improved by iterating over map keys:

for (let k of map.keys()) {
  if (!(k % 2))
    map.delete(k);
}

Solution 3 - Javascript

// Given a map with keys a, b

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

// Return a new map that do not match key a

new Map([...m].filter(([k, v])=>k!=='a'))

// Return a new map only matching value 2

new Map([...m].filter(([k, v])=>v===2))

// Filter in place, removing all values not 2

[...m].map((i)=>i[1]!==2 && m.delete(i[0]))

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
QuestionPhilipp Cla&#223;enView Question on Stackoverflow
Solution 1 - JavascriptRomanView Answer on Stackoverflow
Solution 2 - JavascriptEstus FlaskView Answer on Stackoverflow
Solution 3 - JavascriptJohn WilliamsView Answer on Stackoverflow