5

I apologize if this has been asked before but I could not find an answer. How do I loop through an array with nested arrays and in the console print out the number of instances an item appears?

So console.log should print out the number 2 for the name "bob" because "bob" appears twice in the array.

Here is my array and what I have so far:

    var names = ["bob", ["steve", "michael", "bob", "chris"]];

    function loop(arr, item) {
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] instanceof Array) {
          loop(arr[i], item);
        } else {
          if (arr[i] == item) {
            console.log(arr[i]);
          }
        }
      }
    }

    loop(names, "bob");
Rajaprabhu Aravindasamy
  • 66,513
  • 17
  • 101
  • 130
stalwil
  • 125
  • 1
  • 1
  • 10
  • 2
    Your recursion looks about fine. Now you just have to count instances (and return that value), instead of `console.log`ging them. Try it! – Bergi Mar 08 '16 at 18:03
  • Possible duplicate of [How to flatten nested array in javascript?](http://stackoverflow.com/questions/27266550/how-to-flatten-nested-array-in-javascript) – jannis Mar 08 '16 at 18:11
  • Also answered here: http://stackoverflow.com/a/15030117/4494577 – jannis Mar 08 '16 at 18:15

6 Answers6

8

here you go, note that you can keep the counter value internally, to keep the rest of your code cleaner:

var names = ["bob", ["steve", "michael", "bob", "chris"]];

function loop(arr, item) {
  var result = 0;
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] instanceof Array) {
      result += loop(arr[i], item);
    } else {
      if (arr[i] == item) {
        result++;
      }
    }
  }
  return result;    
}


var result = loop(names, "bob");
console.log(result);
jberndsen
  • 331
  • 2
  • 4
3

You need a counter

function loop(arr, item) {
    var count = 0;
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] instanceof Array) {
            count += loop(arr[i], item);
        } else {
            if (arr[i] == item) {
                count++;
                console.log(arr[i]);
            }
        }
    }
    return count;
}

var names = ["bob", ["steve", "michael", "bob", "chris"]],
    count = loop(names, "bob");

document.write(count);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
3

You could also use reduce

var names = ['bob', ['steve', 'michael', 'bob', 'chris', ['bob']]];

function count(item, items) {
  return items.reduce(function(sum, x) {
    if (Array.isArray(x)) return sum + count(item, x);
    if (x === item) return sum + 1;
    return sum;
  }, 0);
}

count('bob', names); // => 3

Another option would be to use more generic functions and chain them together.

  1. flatten the input array; [1,[2,3,4,5,[6]]] => [1,2,3,4,5,6]
  2. filter the flattened array where each element matches your search element
  3. return the length of the filtered array

That would look something like this

flatten(names).filter(x => x === 'bob').length

I'll leave the implementation of flatten as an exercise for you

Mulan
  • 129,518
  • 31
  • 228
  • 259
0

A pure form of recursion, No outside variable required.

Try to modify your logic like below,

var names = ["bob", ["steve", "michael", "bob", "chris",["bob"]]];
function loop(arr, item, cnt){
   cnt = cnt || 0;
   for(var i = 0; i < arr.length; i++){
     if(arr[i]==item){ cnt++; }
     else if(arr[i] instanceof Array) { return loop(arr[i], item, cnt); } 
   }
   return cnt;
}

loop(names,"bob"); //3

var names = ["bob", ["steve", "michael", "bob", "chris", ["bob"],["bob",["bob"]]], "bob"];

function find(arr, txt, cnt, match) {
  cnt = cnt || 0;
  match = match || 0;
  if (arr[cnt] === txt) { match++; } 
  else if (arr[cnt].push) { match = find(arr[cnt], txt, 0, match); } 
  if (++cnt === arr.length) { return match; }
  return find(arr, txt, cnt, match);
}

alert(find(names, "michael")); //6
Rajaprabhu Aravindasamy
  • 66,513
  • 17
  • 101
  • 130
  • The introduction of another argument is unnecessary. – Aldehir Mar 08 '16 at 18:11
  • It's an improvement, although I would take the other answers as they don't rely on propagating yet another object down recursion and instead propagate the result up. – Aldehir Mar 08 '16 at 18:16
  • @Aldehir That's their approach and this is mine. There is no necessity to stick with the larger audience. This is not going to make any impact in performance. – Rajaprabhu Aravindasamy Mar 08 '16 at 18:19
  • 1
    This won't work in the case that another matching item exists later on in the top level array, for example `["bob", ["steve", "michael", "bob", "chris",["bob"]], "bob"]`, because it returns too early. Changing `return loop(arr[i], item, cnt);` to `cnt = loop(arr[i], item, cnt);` will fix this. – Apoc Mar 08 '16 at 21:14
0

const names = ["bob", ["steve", "michael", "bob", "chris"]];

function loop(arr, item) {
    let res = 0;

    for (let v of arr) {
        if (typeof v === 'object') res += loop(v, item);
        if (v === item) res++;
    }

    return res;    
}



const result = loop(names, "bob");
console.log(result);
0

As of 2020, we have Array.prototype.flat method which makes this task trivial.

Here you go.

const names = ["bob", ["steve", "michael", "bob", "chris"]];
const nestedNames = ["bob", ["steve", ["michael", ["bob", ["chris"]]]]];

function countName(array, name, depth = Infinity) {
  return array.flat(depth).reduce((acc, cur) => acc + (cur === name ? 1 : 0), 0);
}

console.log(countName(names, 'bob')); // 2
console.log(countName(names, 'chris')); // 1

console.log(countName(nestedNames, 'bob')); // 2
console.log(countName(nestedNames, 'chris')); // 1
kabirbaidhya
  • 3,264
  • 3
  • 34
  • 59