Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly count occurrence in arrays?

I've got an in depth array (arrays of arrays). I'm trying to make a decent function without using any extra methods and JS designs (like closures).

This is a working solution (but a bad one because of the global variable)

  var counter = 0;

  function findArray(array, item) {
      for (var i = 0; i < array.length; i++) {
          if (array[i] == item) {
              counter++;
          } else if (array[i].constructor === Array) {
              findArray(array[i], item);
          }
      }
      return counter;
  }

This is a none working solution with trying to avoid the global counter variable.

  function findArray(array, item, counter) {
    for (var i = 0; i < array.length; i++) {
        if (array[i] == item) {
            ++counter;
        }
        if (array[i].constructor === Array) {
            findArray(array[i], item, counter);
        }
    }
    return counter;
}

I would also like to avoid the counter input parameter since parameters should only be inputs and output variables shouldn't be inside the function params. Also I would like to avoid built in methods for instance slice etc.

NOTE: The optimal performance of the algorithm should not be taken into consideration.

Is there a way to make this work with the counter being inside the function?

function call looks like this:

findArray([1,[1,2,3],1,[5,9,1],6],  1,0);

The output should be number 4, in this example (integer 1 is the number to be searched in the array).

like image 840
Eugen Sunic Avatar asked Dec 23 '22 17:12

Eugen Sunic


2 Answers

This is a use case for recursion. (And also for Array.isArray, which was added in ES5.) findArray can call itself to find occurrences within arrays within the array:

function findArray(array, item) {
    var counter = 0;
    for (var i = 0; i < array.length; i++) {
        var entry = array[i];
        if (entry == item){
            counter++;
        } else if (Array.isArray(entry)) {
            // Add in the count of occurrences in the array, and any arrays in it
            counter += findArray(entry, item);
        }
    }
    return counter;
}

It's also a use case for Array#reduce:

function findArray(array, item) {
    return array.reduce(function(counter, entry) {
        if (entry == item) {
            ++counter;
        }
        if (Array.isArray(entry)) {
            counter += findArray(entry, item);
        }
        return counter;
    }, 0);
}

Array#reduce calls the callback once per entry in the array, passing in the accumulator on each call. The initial value of the accumulator is provided as a second argument (optional in some cases, but not here); the callback returns an updated version of the accumulator, which is the ultimate return value of reduce.

like image 195
T.J. Crowder Avatar answered Jan 02 '23 01:01

T.J. Crowder


You have the right idea. You can modify the first recursive function like this:

function findArray(array, item) {
  var occurrences = 0;

  for (var i = 0; i < array.length; i++) {
    if (array[i] == item) {
      // Found an occurrence.
      occurrences++;
    } else if (array[i].constructor === Array) {
      // Add all occurrences in the sub-array i
      occurrences += findArray(array[i], item);
    }
  }

  return occurrences;
}


console.log(findArray([1, [1, 2, 3], 1, [5, 9, 1], 6], 1, 0));

Or shorten it using a reduce call, which has the same idea as above:

function findArray(array, item) {
  return array.reduce((occur, e) =>
    occur + ((Array.isArray(e)) ? findArray(e, item) : (e == item)), 0);
}

console.log(findArray([1, [1, 2, 3], 1, [5, 9, 1], 6], 1, 0));
like image 31
John Bupit Avatar answered Jan 02 '23 03:01

John Bupit