JavaScript Iterators: Solving The Same Problem 6 Different Ways
The problem was quite simple. Yet, it took me twice the time I thought it would. I tried it from different angles, explored various approaches and even combined them into a solution (mistakenly thinking this would somehow work).
I thought I might be misunderstanding the problem, so I reread it for the third time:
“You get an array of numbers, return the sum of all the positive ones.” Example:
[1,-4,7,12] => 1 + 7 + 12 = 20
The difficulty came from the range of tools I could use for the job. I first thought of a for loop
with an if-conditional
. Simple and reliable.
But what if there was a more elegant way. I could use an array method to get the job done. Maybe a .reduce()
could work. Since we are looping through an array, why don't I keep things simple with the for...of
loop?
I was getting annoyed with my indecisiveness. I just pushed myself to crank out a solution:
const positiveSum = arr => {
let sumOfPositive = 0;
for (let i of arr) {
if (i>=0)
sumOfPositive += i;
}
return sumOfPositive;
}
array = [1, 3, -3, 4, 5, 6, 10];
positiveSum(array);
//result: 29;
I knew this wasn’t the most elegant work, but at that point, I didn’t care anymore.
I moved on with my day, but something kept nagging at me. In the back of my head, I kept asking myself: how else could have I solved this problem? I had options, but which one was the best?
I fired up my computer and started testing different ways I could use to solve the problem. The exercise came from Code Wars, which meant alternative solutions were available.
Some were way more elegant than mine, while others were obvious overkills. Nevertheless, they all worked and returned the same result. In the context of a bigger codebase, the specific approach may have mattered, but in this case, it didn’t.
Part of any coding journey is learning how to achieve the same result in many different ways. So, I tried every single one of the solutions I could think of. Below are some of the ones I enjoyed testing out the most.
Using The .reduce()
method
const positiveSum = arr => {
return arr.reduce((a,b) => a+ (b>0 ? b : 0),0);
}
This was my favourite solution. It’s an elegant one-liner that packs so much power.
The .reduce()
method takes a function (a callback function) with two parameters - a previous value (a
) and a current value (b
). Since the method applies only to arrays, we refer to elements of the array when we speak of values here.
.reduce()
takes each element in the array and applies to it the function it holds ((b>0 ? b : 0)
. In our case, the method takes an element (b
), tests if it is greater than zero, and if it is, it adds it to the previous element (a
). If it is not greater than zero, the function adds 0
to a
.
The method adds up all the positive values in the array.
The first time I learned about .reduce()
, something didn't quite make sense. If the method is assessing each element from left to right and always uses a previous value and a current value, what is the previous value of the element with index 0? We need some previous value to add the value with index [0]
to.
It turns out that we need to declare an initial value. This is the last 0
you see on this line:
return arr.reduce((a,b) => a+ (b>0 ? b : 0),0);
If we don’t declare an initial value, the value in [0]
becomes the initial value. Usually, when you add up a number, you want to start adding to a value of 0
, so declaring an initial value of 0
makes sense.
Using .reduce()
+ Math.max()
In the previous solution, we used a conditional (ternary) operator to test whether a given element in the array is greater than 0
. If it is, we add it up to the previous value; if it's not, we add 0
. In other words, for each element, we pick the larger of 1) the element's value or 2) 0
or the max value.
We have a method in JavaScript to do this — Math.max()
. This is how we can use it in the .reduce()
method to add only the positive numbers:
const positiveSum = arr => {
return arr.reduce((a, b) => a + Math.max(b,0),0);
}
Simple. Elegant. Beautiful.
Using .filter()
+ .reduce()
The beauty of the .reduce()
method is that it allows us to combine the elements in an array in different ways. The starting point, however, is always an array.
Our problem starts with an array, which includes potentially “dirty” data. We need to add only the positive value, and the array we are working with may include any kind of value. We can use .filter()
to clean up our array. The method filters the elements of the array based on a specific condition and returns a new array only with the elements meeting the condition.
To solve our problem of adding only the positive numbers, we can call on the .filter()
and .reduce()
methods to do our dirty work:
.filter()
to clean up our array;reduce()
to add the elements in the array.
Here’s how we can do it:
const positiveSum = arr => {
return arr.filter(i => i >= 0).reduce((a,b) => a+b,0);
}
We grab our array (arr
) filter it for only positive values (arr.filter(i => i >= 0
) and place them in an anonymous array. We take that new array and reduce its elements to one number, which is their sum. (.reduce((a,b) => a+b,0)
).
If you look at the syntax, you’ll notice something interesting:
We take an array, apply a method, and then apply another method. We are chaining methods (or functions, since methods are functions).
Chaining .filter().reduce()
works because .filter()
returns an array and .reduce()
needs an array to work with. If filter()
didn't return an array but a string or a boolean, you'll get a typeError
or unexpected token
error.
Using .forEach()
In the previous solutions, we used methods that return the value we wanted the function to produce. The .forEach()
method does things differently.
The method performs a function on each element once. The method itself returns undefined
, which is not useful to us. This is why, if we want to use.forEach()
, we need to do it in a particular way:
const positiveSum = arr => {
let sumOfPositive = 0;
arr.forEach((a, i) => (a >=0) ? sumOfPositive += a :i++);
return sumOfPositive;
}
In this solution, we declare a variable let sumOfPositive = 0
, which binds to a value of0
. We need this variable to hold the sum of the outputs produced by .forEach()
. The function in .forEach()
each variable and makes a decision: if positive, add it to sumOfPositive
; if not, move on to the next element.
Unlike in the previous solutions, we want to return sumOfPositive
, not the method. The value we are after is stored in sumOfPositive
. .forEach()
itself returns undefined
.
Using the Good Old for loop
All solutions above require familiarity with specific methods and callback functions (for-of loop
is the exception). There is a way to complete this exercise without methods.
Re-examining the ask, all we have to do is loop through an array and add up the positive numbers. Without complicating our lives with methods, we can use the for loop
:
const positiveSum = arr => {
let sumOfPositive = 0;
for (let i = 0; i<arr.length; i++) {
if (arr[i]>=0)
sumOfPositive += arr[i];
}
return sumOfPositive;
}
Like in the previous solution, here, we also have a variable to hold the sum, and we iterate through each array element. Unlike in the previous solutions (for-of
and .forEach
), the for loop
doesn't know when to stop looping. This is why we set a limiter equal to the length of the array (i<arr.length
).
As we saw, we can solve the same problem in many different ways. All solutions gave us the same answer but are there one that we can call “the best”?
Looking for the best solution to a problem is like looking for the best pizza for lunch. Our preference for pizza and one solution over another depends on the situation: the surrounding codebase, the knowledge of our options and intuition. If the simple for loop
makes the most sense, this is the best choice.
Like with pizza, as long as our function gives us the same predictable result, we should be happy.