loading...

Much needed filterMap in JavaScript

akashkava profile image Akash Kava ・1 min read

Most often we perform filter followed by map and it turns out that combining both in one function would certainly avoid multiple iteration.

Array.prototype.filterMap = function(filter) {
   const r = [];
   for(let i=0; i<this.length; i++) {
      const item = filter(this[i], i, this);
      if (item !== undefined) {
         r.push(item);
      }
   }
   return r;
};

// usage..

emails = customers.filterMap(
   (c) => c.active ?
      { name: c.name, email: c.email } :
      undefined);

// as opposed to

emails = customers.filter( (c) => c.active)
            .map( (c) => ({ name: c.name, email: c.email }));

Enter fullscreen mode Exit fullscreen mode

Ideally we can change behavior of Array.map to adapt Array.filterMap but we don't know what impact it will have on numerous libraries.

Perf: https://jsperf.com/array-filtermap/1

Discussion

pic
Editor guide
Collapse
avalander profile image
Avalander

If I were to implement something like filterMap, I would pass in the predicate and the transformation as separate functions. However, you can use reduce for what you're doing, no need to implement a new method on Array.

const emails = customers.reduce(
  (prev, c) => c.active
    ? [ ...prev, c ]
    : prev,
  []
)
Collapse
akashkava profile image
Collapse
avalander profile image
Avalander

Yeah, sorry, been using array destructuring too much lately. If it's speed we're going after we can't create a new array at each iteration step.

const emails = customers.reduce(
  (prev, c) => {
    if (c.active) prev.push(c)
    return prev
  },
  []
)
Thread Thread
akashkava profile image
Akash Kava Author

This way reduce can be used as map as well, nice trick but I still prefer filterMap for its smaller syntax.

Collapse
joelnet profile image
JavaScript Joel

The same exact logic but written with reduce instead of a for loop.

Array.prototype.filterMap = function(filter) {
  return this.reduce((acc, x, i) => {
    const item = filter(x, i, this);
    if (item !== undefined) {
      acc.push(item)
    }
    return acc
  }, [])
}

more info: Map, Filter, Reduce vs For Loops (syntax)

Also while this function is very cleaver, it is now considered an anti-pattern to modify the prototype of built in objects.

We want to avoid any "smoosh gate" fiasco in the future.

Collapse
akashkava profile image
Akash Kava Author

Well we can have an independent function with array being first parameter. I really don’t care about anti pattern because extending prototype is the best feature of JS. Otherwise everything is possible in other languages

Collapse
joelnet profile image
JavaScript Joel

Well we can have an independent function with array being first parameter.

That is what I would recommend

I really don’t care

But see, you should. Something seemingly harmless as extending a prototype has cause much difficulty in the JavaScript community due to name collisions.

An older but popular library extended Array to include flatten. The problem was this version conflicted with the implementation proposed by the TC39. Because the TC39 could not use flatten without breaking the internet, the dumb name smoosh was recommended. github.com/tc39/proposal-flatMap/p...

This has happened multiple times.

Today the TC39 is currently arguing over a name for a global context object. Because node has called their global object global, this name cannot be used without breaking the internet again. So they are currently proposing the horrible name of globalThis github.com/tc39/proposal-global/is...

While your code may have little impact now. It could have very large effects in the future.

So you should definitely care.

Cheers!

Collapse
johnsoncherian profile image
johnsoncherian
Array.prototype.filterMap = function(filter) {
   const r = [];
   for(let i=0; i<this.length; i++) {
      const item = filter(this[i], i, this);
      if (item !== undefined) {
         r.push(item);
      }
   }
   return item; // should be `return r`. right.?
};```

Collapse
akashkava profile image
Akash Kava Author

Yes you got that right !!

Collapse
iliasbhal profile image
ilias bhallil

There is the flatMap method that does exactly that filter + map :)
this method not only lets you dynamically filter the array, but it also gives the ability to add elements to the array dynamically.

developer.mozilla.org/en-US/docs/W...

// example
someArray.flatMap((element) => {
  if(element.isNotNeeded){
    return [];
  }

  return [ element.someProperty ];
})
Collapse
alexaegis profile image
Győri Sándor

Ideally, filterMap shouldn't restrict me from mapping my elements into anything I could map it to. Including undefined.

[1, 2, 3, 4].filterMap(n => n % 2, n => `${n} is even`)

This would be a better Api, really similar to a separate filter and map, but with one loop inside.