3

I have a function that needs to sum all numbers in an arrays, if those numbers are strings '1', '2' the function needs to sum those too.

I have written a function and tried parsing the numbers but it doesn't work. basically, it needs to sum up all numbers. Can you do it without using reduce? I need a simple solution or a solution with .map Where is my mistake?

    function sumArrNums(arr) {
        let count = 0;
        arr.forEach(el => typeof el == 'number' ? count+= el : '');
        return count;
    }
    
    console.log(sumArrNums(['1', '2', 5,5,5, '3']))
georg
  • 211,518
  • 52
  • 313
  • 390
  • You already know that `el` is a number or not. Instead of only adding the numbers, convert the non-numbers first. – Andreas Sep 11 '19 at 16:25
  • Here is a solution that does not work either: function sumNumsAndStrNums(arr) { count = 0; arr.split(',').map(function(el){ return count += el; }); console.log(sumNumsAndStrNums(['5', '5', '5', 5, 5, 5])) – Andrey Bondarev Sep 11 '19 at 16:25
  • `I need a simple solution or a solution with .map` no map is not a right choice for this job, `forEach` is fine but IMO reduce is better – Code Maniac Sep 11 '19 at 16:40
  • @CodeManiac `arr.map(Number).reduce((a, b) => a + b)` - a solution with `map`. – VLAZ Sep 11 '19 at 16:41
  • @VLAZ i will not prefer looping over the same array twice just to do number conversion first and than adding – Code Maniac Sep 11 '19 at 16:43
  • @CodeManiac as I said elsewhere, I doubt *this* problem needs to be highly optimised. – VLAZ Sep 11 '19 at 16:44
  • @VLAZ well this not high optimization at all, i agree this is primarily opinion based but i don't see any reason to use `map` just for number conversion, when we already have `reduce` or `forEach` which serves in this purpose very well – Code Maniac Sep 11 '19 at 16:51
  • @CodeManiac I disagree, avoiding two loops is extremely high optimisation in this case. I sincerely doubt OP is going to run into any performance issues here. I doubt this is production code and this seems like an exercise. So, optimising it is superfluous at best. Besides, even with production code I don't think doing a `.map().reduce()` is bad in a lot of cases - hardly an overhead in a lot of cases and the JS environment might even optimise it for you. Summing an array is a solved problem, as is converting a string to a number. I don't see the need to reinvent both into one solution. – VLAZ Sep 11 '19 at 16:58

5 Answers5

3

Your ternary operator is doing nothing when the element is a string, you can use Number(el) (or unary +) to convert elements to numbers (strings will be converted, and numbers will remain numbers, so there is no need for type checking):

function sumArrNums(arr) {
    let count = 0;
    arr.forEach(el => count += Number(el));
    return count;
}

console.log(sumArrNums(['1', '2', 5, 5, 5, '3']))
DjaouadNM
  • 22,013
  • 4
  • 33
  • 55
  • Or do the conversion first with `.map(Number)` – VLAZ Sep 11 '19 at 16:25
  • @VLAZ That's good if you want to use the converted array in later calculations, but in this case, converting goes through the array, then `.forEach` goes through it again. – DjaouadNM Sep 11 '19 at 16:29
  • I don't think *this* problem is in need of optimisation. – VLAZ Sep 11 '19 at 16:32
  • @VLAZ You're right, the gain may not be that noticeable but if you could do it in one go, why do it in two. – DjaouadNM Sep 11 '19 at 16:33
  • 1
    Because, in many situations I don't find the unary plus more readable. `count += +el` makes me go back and forth to see if it's not actually an addition and realising it's not but it's it's an addition assignment. It's quite more annoying to read since I can't tell *exactly* what's happening at a glance. I can understand a single addition assignment even without reading it. – VLAZ Sep 11 '19 at 16:39
  • @VLAZ It it's for the `+`, I've changed it to `Number` for better readability, you're right about that. – DjaouadNM Sep 11 '19 at 16:53
2

You can use isNaN to check if the number or string can be parsed to string or not, and than add values

Here + before el does implicit conversion from string to number

function sumArrNums(arr) {
  let count = 0;
  arr.forEach(el => count += !isNaN(el) ? +el : 0);
  return count;
}

console.log(sumArrNums(['1', '2', 5, 5, 5, '3', {}, '1a', [] ]))
Code Maniac
  • 37,143
  • 5
  • 39
  • 60
2

I'd like to post a "meta"-answer, pointing at some archetypal mistakes made by you and other posters and frequently seen in other code reviews.

Mistake 1: unary +

Unary plus seriously hurts readability, especially in combination with other operators. Please do your readers (including your a few months older self) a favor and use the Number function - this is what it's for:

+a + +b   //  wtf?

Number(a) + Number(b) //  copy that

Apart from readability, Number(x) is identical to +x in every way.

Mistake 2: not checking for NaNs

Number conversions can fail, and when they fail, they return a NaN and NaNs are sticky, so this will return NaN despite valid numbers being present in the array:

[1, 2, 'blah'].reduce((a, b) => Number(a) + Number(b)) //  =NaN

This will be better (in the context of summation, NaN can be considered 0):

[1, 2, 'blah'].reduce((a, b) => (Number(a) || 0) + (Number(b) || 0)) //  =3

Mistake 3: not checking for empty values

Unfortunately, Number is broken in javascript. For "historical reasons" it returns 0 when given null or an empty string. For the summation function it doesn't matter, but it will bite you once you decide to use similar code for multiplication.

Mistake 4: reduce with no initial value

array.reduce(func) looks tempting, but unfortunately it doesn't work with empty arrays

[].reduce((a, b) => a + b) //  TypeError: Reduce of empty array with no initial value

so consider the init mandatory:

[].reduce((a, b) => a + b, 0) //  returns 0

Mistake 5: wrong iteration method

The choice between iteration methods (forEach/map/filter/reduce) is tough sometimes, but this simple set of rules should help in most cases:

  • use map to convert a number of things to the same number of other things
  • use filter to convert a number of things to a lesser number of the same things
  • use reduce to convert a number of things to one other thing
  • do not use forEach

For example, this:

result = [];
array.forEach(item => result.push(do_something(item))) // 

is an "antipattern" and should actually be map:

result = array.map(do_something) // 

Similarly, this

result = 0;
array.map(item => result = result + item)) // 

should be

result = array.reduce((res, item) => result + item, 0) // 

Putting it all together

Our assignment basically consists of three parts:

  • convert all elements in the array to numbers
  • remove those that couldn't be converted
  • sum the rest

For the first step we use map, then filter, then reduce:

let sumNumbers = a => a
    .map     (x => Number(x))        // convert to numbers
    .filter  (x => !Number.isNaN(x)) // remove NaN's
    .reduce  ((s, x) => s + x, 0)    // sum

On a more advanced note, with a couple of helpers we can also write this "point-free", without arrow functions:

let not = fn => x => !fn(x);
let add = (x, y) => x + y;

let sumNumbers = a => a
    .map(Number)
    .filter(not(Number.isNaN))
    .reduce(add, 0)
georg
  • 211,518
  • 52
  • 313
  • 390
1

Use unary + operator to convert your strings to numbers:

const sumArrNums = arr => arr.reduce((sum, num) => sum + +num, 0)

console.log(sumArrNums(['1', '2', 5,5,5, '3']))
Marcos Luis Delgado
  • 1,289
  • 7
  • 11
0

Your code is okay, you just need to ensure that you coerce the strings to number. There are lots of ways to do that, in your case you might use the unary +:

function sumArrNums(arr) {
    let count = 0;
    arr.forEach(el => {
      count += +el;
    })
    return count;
}

console.log(sumArrNums(['1', '2', 5,5,5, '3']))

and yes, this is one of the few really solid use cases for reduce:

function sumArrNums(arr) {
    // NOTE: Assumes at least one entry! More below...
    return arr.reduce((a, b) => +a + +b);
}

console.log(sumArrNums(['1', '2', 5,5,5, '3']))

Note there we're coercing both arguments to the callback, since on the first call they'll be the first two entries in the array (after that, the first argument will be the previously returned value, a number, but using + on it is a no-op so it's fine).

That code assumes that arr will have at least one entry. If it doesn't, reduce will fail because if you don't provide an initial value for the accumulator and there aren't any elements in the array, it doesn't have any value to return. If you want to return 0, the simplest thing is to provide the initial value, which also means you don't have to apply + to the accumulator:

function sumArrNums(arr) {
    return arr.reduce((acc, value) => acc + +value, 0);
}

console.log(sumArrNums(['1', '2', 5,5,5, '3']))

If you want to return something else (like NaN) for the case where the array has no entries, you probably want to branch:

function sumArrNums(arr) {
    return !arr.length ? NaN : arr.reduce((a, b) => +a + +b);
}

console.log(sumArrNums(['1', '2', 5,5,5, '3']))
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875