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?
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.
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;
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.
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'))
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
];