1

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?

6
  • What are you actually trying to do? Only reason you have to filter out undefined values from the serverlist is because you conditionally return a value from the callback function of map(). If you need to find a particular item in the array, then you can use the find() method. Commented Sep 8, 2021 at 5:32
  • "How do I do that?" - You are mistaken - the problem you're describing is not what reduce is for. reduce is for when you want to reduce the entire list to a single-value, which is not what you're doing. Just chain map and filter together. Commented Sep 8, 2021 at 5:37
  • @Yousaf actually I have an array of objects and I want to filter out only the Hostname property only when productId is 1. And then I want to sort the hostname. When I map over in the 1st statement, I received undefined as well since those arrays did not have productId value as 1. So I filtered out the undefined values Commented Sep 8, 2021 at 5:38
  • If you only need the Hostname property of the object with productId 1, then why not just filter the object with productId 1? Why use map()? Commented Sep 8, 2021 at 5:40
  • @Yousaf The OP didn't say that there would only ever be a single a object with `productTypeId === '1'. Commented Sep 8, 2021 at 5:41

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

13 Comments

Thanks a lot for the detailed solution. I get it that I should proceed with map and filter.But actually I was asked to use reduce instead of map and filter. So, I will have to go with the other answer. Thanks once again for the help :)
@pranami There is no advantage to using reduce though - as I wrote in a comment-reply to the other answer: your script will likely run more slowly because browsers/engines cannot optimize .reduce the same way they can .filter+.map. Please tell us why you feel you have to use .reduce - it seems to me like you're on a Fool's Errand.
@Yousaf The OP said they were "asked to" - presumably by their boss or client. I wish more people saw their job as working together to find the best solution for their boss/client instead of simply following orders, unquestioned. le sigh.
@Dancrumb Your code produces an array-of-arrays, not a single array, and oddly when I run your code in JSFiddle the end-result is an empty array.
@Dancrumb And for good-measure, for, for-of, etc - Naturally, for is somehow far, far faster than anything else and as an FP-adherent my disappointment is immeasureable and my day is ruined.
|
1

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)

7 Comments

Your use of reduce is identical to filter+map though - but now your function can't take advantage of optimized filter implementations provided by the JS runtime (e.g. it isn't parallelizable anymore).
@Dai hmm, as far as I know, the OP only mentioned rewriting into reduce, rather than mentioned anything about further optimization or anything else. So I do not take those other things in my answer
Yep - except I argue that the OP is wrong to want to rewrite it to use reduce in the first place ;)
Thanks a lot for the solution . Its working. I understand the discussion about not using reduce in this case, but I was asked to replace the statements with reduce. So this answer helped :)
NodeList is an existing js class. It shouldn't be used as a variable name. developer.mozilla.org/en-US/docs/Web/API/NodeList
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.