2

I'm trying to sum a nested array [1,2,[3,4],[],[5]] without using loops but I don't see what's wrong with what I have so far.

function sumItems(array) {
  let sum = 0;
  array.forEach((item) => {
    if (Array.isArray(item)) {
      sumItems(item);
    } else {
      sum += item;
    }
  });
  return sum;
}
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
jj008
  • 1,033
  • 3
  • 23
  • 48

7 Answers7

4

try with

 function sumItems(array) {

  let sum = 0;
  array.forEach((item) => {
    if(Array.isArray(item)) {
     sum += sumItems(item);
    } else {
    sum += item;
    }
  })
  return sum;
}
4

recursion is a functional heritage

Recursion is a concept that comes from functional style. Mixing it with imperative style is a source of much pain and confusion for new programmers.

To design a recursive function, we identify the base and inductive case(s).

  • base case - the list of items to sum is empty; ie, item is Empty. return 0
  • inductive case 1 - the list of items is not empty; ie, there must be at least one item. if the item is a list, return its sum plus the sum of the rest of the items
  • inductive case 2 - there is at least one item that is not an array. return this item plus the sum of the rest of the items

const Empty =
  Symbol ()

const sumDeep = ([ item = Empty, ...rest ] = []) =>
  item === Empty
    ? 0
    : Array.isArray (item)
      ? sumDeep (item) + sumDeep (rest)
      : item + sumDeep (rest)

console.log
  ( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
  , sumDeep ([ 1, 2, 3, 4, 5, 6 ])                     // 21
  , sumDeep ([])                                       // 0
  , sumDeep ()                                         // 0
  )

As a result of this implementation, all pain and suffering are removed from the program. We do not concern ourselves with local state variables, variable reassignment, or side effects like forEach and not using the return value of a function call.


recursion caution

And a tail-recursive version which can be made stack-safe. Here, we add a parameter cont to represent our continuation which effectively allows us sequence the order of + operations without growing the stack – changes in bold

const identity = x =>
  x

const sumDeep = ([ item = Empty, ...rest ] = [], cont = identity) =>
  item === Empty
    ? cont (0)
    : Array.isArray (item)
      ? sumDeep (item, a =>
         sumDeep (rest, b =>
           cont (a + b)))
      : sumDeep (rest, a =>
          cont (item + a))

Usage is identitcal

console.log
  ( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
  , sumDeep ([ 1, 2, 3, 4, 5, 6 ])                     // 21
  , sumDeep ([])                                       // 0
  , sumDeep ()                                         // 0
  )

performance enhancement

As @גלעד ברקן points out, array destructuring syntax used above (eg ...rest) create copies of the input array. As demonstrated in his/her answer, an index parameter can be used which will avoid creating copies. This variation shows how the index technique can also be used in a tail-recursive way

const identity = x =>
  x

const sumDeep = (items = [], i = 0, cont = identity) =>
  i >= items.length
    ? cont (0)
    : Array.isArray (items [i])
      ? sumDeep (items [i], 0, a =>
          sumDeep (items, i + 1, b =>
            cont (a + b)))
      : sumDeep (items, i + 1, a => 
          cont (items [i] + a))

console.log
  ( sumDeep ([ [ 1, 2 ], [ 3, 4 ], [ 5, [ 6, [] ] ] ]) // 21
  , sumDeep ([ 1, 2, 3, 4, 5, 6 ])                     // 21
  , sumDeep ([])                                       // 0
  , sumDeep ()                                         // 0
  )
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • `...rest` makes copies, this is inefficient memory use, although it may not be an issue, practically. – גלעד ברקן Feb 14 '18 at 20:56
  • @גלעדברקן that is correct, tho copies are not retained in memory. your approach using an index is a nice workaround :) – Mulan Feb 16 '18 at 21:28
1

Here's a version without using loops:

function f(arr, i){
  if (i == arr.length)
    return 0;
 
  if (Array.isArray(arr[i]))
    return f(arr[i], 0) + f(arr, i + 1);
   
  return arr[i] + f(arr, i + 1);
}

console.log(f([1,2,[3,4],[],[5]], 0));
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
0

You could define a callback for using with Array#reduce, which check if an item is an array and uses this function again for that array.

function add(s, v) {
    return Array.isArray(v)
        ? v.reduce(add, s)
        : s + v;
}

var array = [1, 2, [3, 4], [], [5]];

console.log(array.reduce(add, 0));
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You may do as follows;

var sumNested = ([a,...as]) => (as.length && sumNested(as)) + (Array.isArray(a) ? sumNested(a) : a || 0);

console.log(sumNested([1,2,3,[4,[5,[6]]],7,[]]));

The function argument designation [a,…as] means that when the function is fed with a nested array like [1,2,3,[4,[5,[6]]],7,[]] then a is assigned to the head which is 1 and as is assigned to the tail of the initial array which is [2,3,[4,[5,[6]]],7,[]]. The rest should be easy to understand.

Redu
  • 25,060
  • 6
  • 56
  • 76
0
function arraySum (array) {
  if (array.length > 0) {
    return arraySum(array[0]) + arraySum(array.slice(1));
  }
  if (array.length === 0) {
    return 0;
  } else {
    return array;
  }
};
0

This is similar to some of the other solutions but might be easier for some to read:

 function Sum(arr) {
   if (!arr.length) return 0;
   if (Array.isArray(arr[0])) return Sum(arr[0]) + Sum(arr.slice(1));
   return arr[0] + Sum(arr.slice(1));
 }

 console.log(Sum([[1],2,[3,[4,[5,[6,[7,[8,9,10],11,[12]]]]]]])) // 78
Ali
  • 7,297
  • 2
  • 19
  • 19