简体   繁体   中英

Adding Similar Items Together in a Javascript Array

I'm trying to loop through an array in order to group and count totals.

For example:

var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];

I would like to go through and first see 'Cats', then add all the cat values up, then repeat the process for other unique categories.

The final output would be:

Cats (7)
Mice (2)
Dogs (5)

Currently, I'm trying to accomplish it this way, but I'm obviously making a rookie mistake somewhere.

var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
var animalCounter = function(array){
    var list = '';
    for(var i = 0; i<array.length; i++){
        var animalID = array[i][0];
        var animalCount = 0;
        for (var x; x<array.length; x++){
            if(animalID == array[x][0]){
                animalCount += array[x][0] - 1;
            }
            list += animalID + " (" + animalCount + ")\n";
        }

    }
    alert(list);
}
animalCounter(farm);

use an object to add similar animals together

var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);

var o = {};

for (var i=farm.length; i--;) {
    var key = farm[i][0],
        val = farm[i][1];

    o[key] = key in o ? o[key] + val : val;
}

FIDDLE

Then it's easy to create the list

for (var key in o) {
    list += key + " (" + o[key] + ")\n";
}

FIDDLE

You're missing an outer layer of brackets:

var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];

What you had will end up being the same as

var farm = ['Dogs', 5];

The comma operator does strange things, especially in a var statement because , also separates individual variable declarations and initializations.

Just for funsies... (not very practical)

var farm = [['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]];

farm.reduce(function(a, b, i){
    var r = (i==1 ? a.slice() : a), 
        j = r.indexOf(b[0]);
    if(j >= 0){ 
        r[j+1] += b[1];
        return r;
    } else return r.concat(b); 
}).reduce(function(a, b, i){
    return i%2 ? a+' ('+b+')' : a+'\n'+b;
});

Rough explanation:

Iterate over each element of farm reducing the 2D array to a flat array where every odd index is the "count" that corresponds to the previous element - taking note to check if the "key" in the even index already exists in the array (in which case update it's count respectively) . The slice call is in there just to make sure that the original array is left unmodified. This results in an array looking like:

    ["Cats", 7, "Mice", 2, "Dogs", 5]

This new array is reduced once more, this time concatenating each element into a single string - formatting dependent on whether or not the current iteration has an odd or even index.

Array.reduce is one of those functions that isn't supported in older browsers (if that is important) but there's a polyfill available on the MDN site.

I'd probably do it a bit differently:

var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);

var animalCounter = function(array){
  var animalObject = {};
  for(var i = 0; i<array.length; i++){
    var animalID = array[i][0];
    var animalCount = array[i][1];
    if(animalObject[animalID]) {
      animalObject[animalID] += animalCount;
    } else {
      animalObject[animalID] = animalCount;
    }
  }
  return animalObject;
}

The first function, animalCounter , creates an object that maps animal names to the numbers in the array. So for your example, it will return an object that looks like this:

{ 'Cats': 7, 'Mice': 2, 'Dogs': 5 }

From there, once you have the object, it's trivial to create a string to output this data in whatever format you like:

var counter = animalCounter(farm);
var list = '';
for(var key in counter) {
  list += key + ': ' + counter[key] + ', ';
}
console.log(list); //=> Cats: 7, Mice: 2, Dogs: 5,

The reason your initial function didn't work is because it didn't take into account that there might be more than one instance of the same animal in your array. If your original array was [['Cats', 7], ['Mice', 2], ['Dogs', 5]] it would have been fine, but because your Cats entry was split into ['Cats', 4], ['Cats', 3] , your original code saw that as two distinct animals. Notice in my function:

if(animalObject[animalID]) {
  animalObject[animalID] += animalCount;
} else {
  animalObject[animalID] = animalCount;
}

The code checks to see if the animal is already stored in the new object, and if it is, increments the count, rather than creating a new counter from 0. This way it deals with any duplicates.

I see three problems:

  1. setting animalCounter equal to the number of animals - the new number of animals replaces whatever might already have been stored in animalCounter, so nothing is added up here
  2. creating the animalCounter variable within the loop - if var animalCount is inside the loop, then you actually have a completely new variable for each element of the array
  3. using a single variable for all the types of animals

Instead, try this:

var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);

var animalCounter = function (array) {
    var list = '',
        catsCount = 0,
        miceCount = 0,
        dogsCount = 0;
    for (var i = 0; i < array.length; i++) {
        var animalID = array[i][0];
        var animalCount = array[i][1];
        if (animalID === 'Cats') {
            catsCount += animalCount;
        } else if (animalID === 'Mice') {
            miceCount += animalCount;
        } else if (animalID === 'Dogs') {
            dogsCount += animalCount;
        }
    }
    list = 'Cats(' + catsCount + ') Mice(' + miceCount + ') Dogs(' + dogsCount + ')';
    alert(list);
}
animalCounter(farm);

There are separate variables for each type of animal, and the value in the array is added onto the correct counter variable.

Or, for a more organized solution:

  1. var farm = []; farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);

    var animalCounter = function (array) { var list = '', animalCounters = {}; for (var i = 0; i < array.length; i++) { var animalID = array[i][0]; var animalCount = array[i][1]; animalCounters[animalID] = (animalCounters[animalID] || 0) + animalCount; }

     for (var id in animalCounters) { list += id + " (" + animalCounters[id] + ")\\n"; } alert(list); 

    } animalCounter(farm);

In this code, the animalCounters variable is an object. JavaScript objects act like associative arrays, which lets us use the animal ID string as a "key" to an integer that is the animal sum. Each type of animal is a property of the animalCounters object, with the sum for that type of animal as its value.

I used some slightly obscure notation here, so I'll explain.

animalCounters[animalID]

This is just a different method of referring to properties of an object. In JavaScript, animalCounters.Cats and animalCounters["Cats"] access the same thing. But, if you don't know for sure that the type of animal will be Cats, you need "Cats" (or whatever other kind of animal) to be in a variable. The animalCounters["Cats"] notation takes a string, so you can say this and it will work:

var animalID = "Dogs";
alert(animalCounters[animalID);// returns animalCounters.Dogs




animalCounters[animalID] = (animalCounters[animalID] || 0) + animalCount;

Here, the (animalCounters[animalID] || 0) is saying that if animalCounters[animalID] already has a value, add that value to animalCount , otherwise add 0 to animalCount . This is necessary because if you try to add animalCounters[animalID] to animalCount before animalCounters[animalID] has been set to anything, the addition won't work right.

When you access the amount of animals of a certain kind you made a simple mistake:

animalCount += array[x][0] - 1; 

farm[x][0] will always return the animal's name which is a string, so when trying to subtract 1 from it it will result in NaN (Not a Number).

Also the first for loop: for(var i; i<array.length; i++){ ... cycles through all the array slots even if they were already counted, so cats would be counted twice so instead of cats counted as 7 they would amount to 14.

You need to create a copy of array and take off the slots already counted. The tricky part is copying the array by value and so that any changes to Temp won't change farm (see Copying Arrays ):

var farm = [];
farm.push(['Cats', 3], ['Cats', 4], ['Mice', 2], ['Dogs', 5]);
function countAnimals(array) {
    var Temp = [];
    var d = 0;
    //This while loop copies the array
    while (d < farm.length) {
        var s = array[d].toString();
        Temp.push(s.split(","));
        d++;
    }
    var list = "";
    var done = 0;
    while (done < array.length) {
        if(Temp[0][1] == "Done") {
            Temp.shift();
        } else {
            var animalID = Temp[0][0];
            var count = parseFloat(Temp[0][1]);
            Temp.shift();
            var i = 0;
            while (i < Temp.length) {
                if(Temp[i][0] == animalID) {
                    count = count + parseFloat(Temp[i][1]);
                    Temp[i][1] = "Done";
                }
                i++;
            }
            list = list + "\n" + animalID + "("+count+")";
        }
        done++;
    }
    alert(list);
}
countAnimals(farm);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM