JavaScript sorting and grouping items in array

2019-09-21 15:55发布

I am trying to do some mapping in JavaScript. What I am trying to do is I am trying to check if the type from arr exists in datasetarr, if existed, I get the index in datasetarr and increment the quantity of that index. If not exists, I add in a new entry in datasetarr. Here is my code:

var datasetarr = [];
var pos;
for(var i = 0; i < arr.length; i++){
    console.log('unsorted ' + arr[i].type + ' ' + arr[i].quantity);
    if(datasetarr.indexOf(arr[i].type) > -1){
        pos = datasetarr.indexOf(arr[i].type);
        datasetarr[pos].quantity += arr[i].quantity;
    }else{
        datasetarr.push({type: arr[i].type, quantity: arr[i].quantity});
    }
}

for(var i = 0; i < datasetarr.length; i++){
    console.log('sorted ' + datasetarr[i].type + ' ' + datasetarr[i].quantity);
}

The output should be:

kitchen appliance 20
home entertainment 8
batteries & lightings 4
home appliance 12

Then, I can get the type and quantity each and store into array for me to plot with chart.

However, with my code above, the things that I am getting is exactly the same as the unsorted one. Any ideas which part of my logic went wrong?

5条回答
可以哭但决不认输i
2楼-- · 2019-09-21 16:26

In general, I'd suggest lodash for this kind of stuff

result = _(arr)
    .groupBy('type')
    .mapValues(xs => _.sumBy(xs, 'quantity'))
    .value();

In this particular case, however, a vanilla JS solution is just as easy:

let result = {};

for (let item of arr)
    result[item.type] = (result[item.type] || 0) + item.quantity;
查看更多
地球回转人心会变
3楼-- · 2019-09-21 16:31

Based on OP question:

I am tryin to check if the type from arr exist in datasetarr, if existed, I get the index in datasetarr and increment the quantity of that index. If not exists, I add in a new entry in datasetarr.

And the clarification in comments:

Q: So basically you want to group array arr by type and sum quantity values? (and output result in datasetarr)
A: Yeah that is what I am trying to achieve.

I think the best and easy way to do it is doing exactly that, check if exist and act accordingly.

var datasetarr = [];
for (var i = arr.length - 1; i >= 0; i--) {
  if (datasetarr.hasOwnProperty(arr[i]["type"])) {
    // exist -> increment
    datasetarr[arr[i]["type"]] = datasetarr[arr[i]["type"]] + arr[i]["quantity"];
  } else {
    // no exist -> add new
    datasetarr[arr[i]["type"]] = arr[i]["quantity"];
  }
}

Expected output for datasetarr variable would be:

datasetarr = {
    "batteries & lightings" => 4,
    "home appliance" => 12,
    "home entertainment" => 8,
    "kitchen appliance" => 21
];
查看更多
Emotional °昔
4楼-- · 2019-09-21 16:40

You can write a generalized aggregate function like this, and call it by specifying the keys that categorize and aggregate, which are type and quantity respectively. Lastly, you specify the reducer function, which is just an aggregate sum, a + b:

function aggregate (array, predicate, value, reducer) {
  // group values by predicate
  var groups = array.reduce(function (groups, item) {
    if (item[predicate] in groups) {
      groups[item[predicate]].push(item[value])
    } else {
      groups[item[predicate]] = [item[value]]
    }

    return groups
  }, {})

  // aggregate each group of values by reducer
  return Object.keys(groups).map(function (group) {
    var summary = {}
    summary[predicate] = group
    summary[value] = groups[group].reduce(reducer, 0)
    return summary
  })
}

// input array
var arr = [{ type: 'kitchen appliance', quantity: 2 }, { type: 'home entertainment', quantity: 2 }, { type: 'home entertainment', quantity: 3 }, { type: 'batteries & lightings', quantity: 2 }, { type: 'home entertainment', quantity: 2 }, { type: 'home appliance', quantity: 5 }, { type: 'kitchen appliance', quantity: 4 }, { type: 'kitchen appliance', quantity: 5 }, { type: 'kitchen appliance', quantity: 3 }, { type: 'kitchen appliance', quantity: 4 }, { type: 'kitchen appliance', quantity: 1 }, { type: 'home entertainment', quantity: 1 }, { type: 'home appliance', quantity: 5 }, { type: 'batteries & lightings', quantity: 2 }, { type: 'kitchen appliance', quantity: 2 }, { type: 'home appliance', quantity: 2 }]

// output array
var sums = aggregate(arr, 'type', 'quantity', function (a, b) { return a + b })

// formatting
var output = sums.map(function (item) {
  return item.type + ' ' + item.quantity
})

// string output
console.log(output.join('\n'))

查看更多
爷、活的狠高调
5楼-- · 2019-09-21 16:43

Use objects for the grouping.

var categories = {};

function incrementBy(category, value) {
  if (!categories[category]) {
    categories[category] = 0;
  }
  categories[category] += value;
}

var datasetarr = [];
var pos;
for(var i = 0; i < arr.length; i++){
  console.log('unsorted ' + arr[i].type + ' ' + arr[i].quantity);
  incrementBy(arr[i].type, arr[i].quantity)
}

for(var category in categories){
  console.log('sorted ' + category + ' ' + categories[category]);
}

You can decompose the object if needed, through it into an array and sort it if required.

A little bit cleaner example, which might let you follow along a little bite better:

var arr = [{type: 'kitchen appliance', quantity: 2},
    {type: 'home entertainment', quantity: 2},
    {type: 'home entertainment', quantity: 3},
    {type: 'batteries & lightings', quantity: 2},
    {type: 'home entertainment', quantity: 2},
    {type: 'home appliance', quantity: 5},
    {type: 'kitchen appliance', quantity: 4},
    {type: 'kitchen appliance', quantity: 5},
    {type: 'kitchen appliance', quantity: 3},
    {type: 'kitchen appliance', quantity: 4},
    {type: 'kitchen appliance', quantity: 1},
    {type: 'home entertainment', quantity: 1},
    {type: 'home appliance', quantity: 5},
    {type: 'batteries & lightings', quantity: 2},
    {type: 'kitchen appliance', quantity: 2},
    {type: 'home appliance', quantity: 2}];


function group(array) {
    var categories = {};

    function incrementBy(category, value) {
        if (!categories[category]) {
            categories[category] = 0;
        }
        categories[category] += value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value.type, value.quantity)
    });

    return categories;
}

function print(categories) {
    for (var category in categories) {
        console.log('%s: %s', category, categories[category]);
    }
}

print(group(arr));

Here a solution of of group hiding the grouping, which than can hold your own implementation:

function group(array) {
    var categories = {};

    function incrementBy(object) {
        var category = categories[object.type];
        if (!category) {
            category = categories[object.type] = {};
        }
        var subCategory = category[object.subType];
        if (!subCategory) {
            subCategory = category[object.subType] = {};
        }
        subCategory += object.value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value, value.quantity)
    });

    return categories;
}

Depending on your use case, you could also flatten the structure:

function incrementBy(object) {
    var key = [object.type, object.subType].join('/');
    var category = categories[key];
    if (!category) {
        category = categories[key] = {};
    }
    subCategory += object.value;
}

But it might make sense to have various maps in place:

function groupings(array) {
    var groupings = {
      types: {},
      subTypes: {},
      paths: {}
    };

    function incrementBy(object) {
        var category = groupings['types'][object.type];
        if (!category) {
            category = groupings['types'][object.type] = {};
        }
        category += object.value;

        var subCategory = groupings['subTypes'][object.subType];
        if (!subCategory) {
            subCategory = groupings['subTypes'][object.subType] = {};
        }
        subCategory += object.value;


        var key = [object.type, object.subType].join('/');
        var path = groupings['paths'][key];
        if (!path) {
            path = groupings['paths'][key] = {};
        }
        path += object.value;
    }

    array.forEach(function (value, index, arr) {
        incrementBy(value, value.quantity)
    });

    return categories;
}

To avoid information loss on aggregations, you could simply create a more complex data structure:

function groupByAndSumBy(data, groupByProperty, sumByProperty) {
  var accumulator = {};

  data.forEach(function(object, index, array) {
      var localAcc = accumulator[groupByProperty]
            = accumulator[groupByProperty] || { items: [] };
      localAcc[sumByProperty]
            = (localAcc[sumByProperty] || 0) + object[sumByProperty];
      localAcc.items.push(object);
  });

  return accumulator;
}

function groupByMerchantNameAndSumByTotalSales(data) {
    return groupByAndSumBy(data, 'merchantName', 'totalSales');
}

This creates an aggregation which also contains the subset of the input array, which allows you a more detailed view on the data.

查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-09-21 16:46

That is because, Your If condition is only checking for index value in the resulted array datasetarr which is already empty for next value to be pushed,

So Your

        if(datasetarr.indexOf(arr[i].type) > -1){
        pos = datasetarr.indexOf(arr[i].type);
        datasetarr[pos].quantity += arr[i].quantity;
    }

will never execute. That is the reason you are getting the same output as input.

查看更多
登录 后发表回答