pranami
pranami

Reputation: 1415

How to replace map and filter with reduce in Javascript

I have this piece of code:

this.serverlist = data.NodeList.map((a) => {
  if (a.productTypeId === "1") {
    return a.HostName;
  }
});

this.serverlist = this.serverlist.filter((x) => {
  return x !== undefined;
});

And I want to replace this 2 statements(.map & .filter) with .reduce. How do I do that?

Upvotes: 1

Views: 3137

Answers (2)

Dai
Dai

Reputation: 155045

And I want to replace this 2 statements(.map & .filter) with .reduce. How do I do that?

You can't[1] and you shouldn't.

The .reduce function is for when you want to reduce the entire collection to a new single value (or object) such as when you're aggregating values (e.g. SUM, Count, etc) or populating a Set<T>... which is not what you're doing here.

Instead just chain filter and map together in that order: use filter first to eliminate the possibility of encountering undefined or invalid input, then use map to extract the gubbins, then followed by sort for good-measure:

Like so:

this.serverList = data.NodeList
    .filter( a => a.productTypeId === '1' && typeof a.HostName === 'string' )
    .map( a => a.hostName )
    .sort();

Note that .filter and .map are FP-style and so don't modify the input data.NodeList array at all and instead always return new Array objects' whereas the .sort() mutates the array in-place (but still returns it).


Footnote 1:

I wrote "you can't" to discourage people from misusing (IMHO) JS's Array's FP-style functions, which are far easier for a JS engine's JIT to optimize when used as stateless pure functions.

I'll admit that technically reduce can still be used here, but it can only work when using reduce as a substitute for for(of) (or .forEach), but which requires you to avoid invalid input by adding conditional logic (which cannot be optimized as easily by the JIT compared to using map and filter as pure FP functions) which kinda defeats the point of using FP-style functions (again, IMO).

For shits and giggles, I ended up writing a .reduce version, benchmarked here (props to @dancrumb for the challenge) and (to my surprise) my .reduce version ("reduce-only-v2" in that jsPerf benchmark) is actually faster than my filter+map version (at least in Chrome 129 x64 on Windows 10) which suggests that Chrome's JIT does not yet apply parallel-computing optimizations to Array's FP functions - but I'll maintain that the filter+map approach is still preferable to .reduce because it's easier to reason about at-a-glance and also because eventually (I hope!) browsers' JITs will be able to optimize filter+map better for many-core processors than .reduce can:

this.serverList = data.NodeList.reduce( ( acc, a ) => {
    if( ( a.productTypeId === '1' ) && ( typeof a.HostName === 'string' ) ) {
        acc.push( a.hostName ); // `Array.prototype.push` returns `.length`, so `return acc.push(..);` won't work here.
        return  acc;
    }
    return acc;
}, /*seed:*/ [] )

and for fun, let's use everyone's favourite JS operator to reduce har-har it down to a 1-liner:

this.serverList = data.NodeList.reduce( ( acc, val ) => ( a.productTypeId === '1' && typeof a.HostName === 'string' ) ? ( acc.push( a.hostName ), acc ) : acc, /*seed:*/ [] );

...which runs in the same time as the above, but will annoy your coworkers.

Upvotes: 3

hgb123
hgb123

Reputation: 14881

I could understand your snippet as

const NodeList = [
  { productTypeId: "1", HostName: "abc.com" },
  { productTypeId: "2", HostName: "abc.com" },
  { productTypeId: "1" },
  { productTypeId: "1", HostName: "xyz.com" },
]

let serverlist = NodeList.map(a => {
  if (a.productTypeId === "1") {
    return a.HostName
  }
})

serverlist = serverlist.filter(x => {
  return x !== undefined
})

console.log(serverlist)
// [ 'abc.com', 'xyz.com' ]

So you could combine to use reduce like this, do filter and get relevant pieces of data in one go

const NodeList = [
  { productTypeId: "1", HostName: "abc.com" },
  { productTypeId: "2", HostName: "abc.com" },
  { productTypeId: "1" },
  { productTypeId: "1", HostName: "xyz.com" },
]

const serverlist = NodeList.reduce((acc, el) => {
  if (el.productTypeId === "1" && el.HostName) {
    acc.push(el.HostName)
  }
  return acc
}, [])

console.log(serverlist)

Upvotes: 1

Related Questions