3

Anyone can explain me this situation?

In the second call the function, the result is {cat: 2, cat,cat: 1, dog: 1, frog: 1}.

However, I thought that the result was going to be {cat: 4, dog: 1, frog: 1}.

What's going on here?

var animals = ['cat', 'cat', ['cat'], 'dog', 'frog'];

var otherAnimals = ['cat', 'cat', ['cat', 'cat'], 'dog', 'frog'];

function reducingArrays(arraySource) {
  var countedData = arraySource.reduce(function(allItems, item) {
    if (item in allItems) {
      allItems[item]++;
    } else {
      allItems[item] = 1;
    }
    return allItems;
  }, {});

  console.log(countedData);
}

reducingArrays(animals); // {cat: 3, dog: 1, frog: 1}

reducingArrays(otherAnimals); // {cat: 2, cat,cat: 1, dog: 1, frog: 1}
// What I expected: {cat: 4, dog: 1, frog: 1}

2
  • 8
    ['cat'] is cast to 'cat'. ['cat','cat'] is cast to 'cat,cat'. You're using arrays as object keys, which converts them to strings. You want some kind of recursive function. Commented Jan 25, 2018 at 13:52
  • 1
    Alternatively use [].concat(...arr) to flatten an array. Commented Jan 25, 2018 at 13:53

5 Answers 5

1

You can convert single element to array:

var animals = ['cat', 'cat', ['cat'], 'dog', 'frog'];

var otherAnimals = ['cat', 'cat', ['cat', 'cat'], 'dog', 'frog'];

function reducingArrays(arraySource) {
  var countedData = arraySource.reduce(function(allItems, item) {
    var arr = Array.isArray(item) ? item : [item];
    allItems[arr[0]] = (allItems[arr[0]] || 0) + arr.length;
    return allItems;
  }, {});

  console.log(countedData);
}

reducingArrays(animals);

reducingArrays(otherAnimals); 

Sign up to request clarification or add additional context in comments.

Comments

0

You have to handle if one of the objects is an array:

function reducingArrays(arraySource){
    var countedData = arraySource.reduce(function (allItems, item) {
        var items = [item];
        if(item.constructor === Array){
            items = item;
        }
        items.forEach(function(item){
            if (item in allItems) {
                allItems[item]++;
            } else {
                allItems[item] = 1;
            }               
        });
        return allItems;
    }, {});
    console.log(countedData);
}

Comments

0

Yoh could use a check for array and use the same callback of reduce for nested arrays.

function reducingArrays(arraySource) {
    return arraySource.reduce(function iter(allItems, item) {
        if (Array.isArray(item)) {
            return item.reduce(iter, allItems);
        }
        allItems[item] = (allItems[item] || 0) +1;
        return allItems;
    }, {});
}


var animals = ['cat', 'cat', ['cat'], 'dog', 'frog'];
var otherAnimals = ['cat', 'cat', ['cat', 'cat'], 'dog', 'frog'];

console.log(reducingArrays(animals));      // { cat: 3, dog: 1, frog: 1}
console.log(reducingArrays(otherAnimals)); // { cat: 4, dog: 1, frog: 1 }

Comments

0

The first comment already explained, that this has to do with how Arrays are stringified since Object keys can only be strings.

Here a way how you can handle nested Arrays in your function.

function countAnimals(source) {
  function _count(allItems, item) {
    if (Array.isArray(item)) {
      return item.reduce(_count, acc);
    }

    if (item in allItems) {
      allItems[item]++;
    } else {
      allItems[item] = 1;
    }
    return acc;
  }

  //needs `source` to be an Array
  return source.reduce(_count, {});

  //this would also work, if `source` is a single animal.
  //like countAnimals('dog');
  //I prefer this approach because it is more flexible 
  //on what the function can deal with, 
  //and it doesn't cost me even a single extra line of code
  //return _count({}, source);
}


var animals = ['cat', 'cat', ['cat'], 'dog', 'frog'];
console.log("simple", countAnimals(animals));

var otherAnimals = ['cat', 'cat', ['cat', 'cat'], 'dog', 'frog'];
console.log("nested", countAnimals(otherAnimals));

//and a deeply nested structure
var moreAnimals = ['mouse', ['cat', ['dog', 'duck', ['frog']], 'cat'], 'dog', 'frog'];
console.log("multiple levels", countAnimals(moreAnimals));
.as-console-wrapper{top: 0;max-height: 100%!important}

Comments

0

If your array is only going to be nested one level deep you can use [].concat(...arr) to flatten it before you iterate over it with reduce.

In this example I've also made the condition a little more concise.

var animals = ['cat', 'cat', ['cat'], 'dog', 'frog'];
var otherAnimals = ['cat', 'cat', ['cat', 'cat'], 'dog', 'frog'];

function reducingArrays(arraySource) {
  return [].concat(...arraySource).reduce(function(allItems, item) {
    allItems[item] = allItems[item] || 0;
    allItems[item]++;
    return allItems;
  }, {});
}

const result = reducingArrays(animals);
const result2 = reducingArrays(otherAnimals);

console.log(result);
console.log(result2);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.