'map(), reduce() and filter vs forEach()
I have just learned about MapReduce, so I wondered if there are any advantages in writing
const initialValue = 0;
if (this.items) {
return this.items.filter(function (item) {
return item && item.quantity && item.price;
}).reduce(function(previousValue, currentValue) {
return previousValue + currentValue.quantity * currentValue.price ;
}, initialValue);
} else {
return initialValue;
}
instead of just
let total = 0;
if (this.items) {
this.items.forEach(function(item) {
if (item && item.quantity && item.price) {
total += item.quantity * item.price;
}
});
}
return total;
Solution 1:[1]
For future readers, there are a few more idiomatic ways to write the reduction in a functional way. These are generally used because they convey intent a bit more cleanly (and don't add a variable to the scope).
Note: I am assuming this.items has type
({ quantity: number; price: number } | undefined)[] | undefined
but each of the examples is tolerant to even more invalid data than the two in the question.
Filtering and mapping before reducing
Default value at the end
return this.items
?.filter(item => item?.quantity && item.price)
.map(item => item.quantity * item.price)
.reduce((a, b) => a + b, 0) ?? 0
Default array at the start
return (this.items ?? [])
.filter(item => item?.quantity && item.price)
.map(item => item.quantity * item.price)
.reduce((a, b) => a + b, 0)
Handling the filter within the map
I would not recommend these just because the previous two convey intention more clearly.
Default value at the end
return this.items
?.map(item => (item?.quantity ?? 0) * (item?.price ?? 0))
.reduce((a, b) => a + b, 0) ?? 0
Default array at the start
return (this.items ?? [])
.map(item => (item?.quantity ?? 0) * (item?.price ?? 0))
.reduce((a, b) => a + b, 0)
Destructuring
Each of the previous examples can be done with destructuring instead. I am including one example.
return (this.items ?? [])
.filter(item => item) // Ensure item exists; sufficient for the cases we need to worry about
.map(({ price = 0, quantity = 0 }) => quantity * price)
.reduce((a, b) => a + b, 0)
Without a map
We can now do the reduction without a map. This can also be done without destructuring, but that is seemingly (to me) inelegant.
return (this.items ?? [])
.filter(item => item)
.reduce((sum, { price = 0, quantity = 0 }) => sum + quantity * price, 0)
Of course, you can change the filter condition, which takes us back to roughly the first example in the question:
return (this.items ?? [])
.filter(item => item?.price && item.quantity)
.reduce((sum, { price, quantity }) => sum + quantity * price, 0)
Original forEach loop
Some of these changes can be made to the original loop, too:
let total = 0;
items?.forEach((item) => {
if (item?.quantity && item.price) {
total += item.quantity * item.price;
}
});
return total;
Solution 2:[2]
I can't see any advantage of the first over the second*. However the second is even faster then the first and looks more clean! The purpose of the first might be to demonstrate the use of built-in array-functions.
However mapreduce is used for a lot of Elements, so you might the speed it up as much as you can. This should be the fastest you can get:
const initialValue = 0;
let total = initialValue;
if (this.items) {
for (var i = this.items.length; i--;) {
let item = this.items[i]
if (item && item.quantity && item.price) {
total += item.quantity * item.price;
}
}
return total;
} else {
return initialValue
}
In addtion you could drop the if inside the loop, if you know that your array is consitant. Both ifs are just there to make sure the array is properly build and the script doesn't run into an Error, that would be usefull for userdata input, but in a closed system you don't need them.
*I noticed that, the second is missing the default value return initialValue
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Avi Mehra |
| Solution 2 |
