Group objects by keys in JavaScript [duplicate] - javascript

This question already has answers here:
Group By and Sum using Underscore/Lodash
(6 answers)
Closed 6 years ago.
I have an array of objects. These objects have a property called "type".
[{
"id": "1",
"type": "Plane",
"date": "date",
"value": "10",
"another_value": "10"
},
{
"id": "1",
"type": "Plane",
"date": "date",
"value": "15",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
}]
Now, I have a table where I want to display this. I want to display it in two rows, one row for "Plane" and one row for "Car" and in that row I want like the total sum of (value, and another_value)
So I'm thinking this is easiest accomplished by somehow going through my original array of objects and grouping them by "type". So I'll get an array of all objects with the type "Car", and one with "Plane".
I have lodash available, then I also need to find a way to sum the values, so I can display it in my table, in a column on each row.
Any advice on how I can accomplish this?

You can do this with plain JavaScript, using methods like map() and filter():
const arr = [{
"id": "1",
"type": "Plane",
"date": "date",
"value": "10",
"another_value": "10"
},
{
"id": "1",
"type": "Plane",
"date": "date",
"value": "15",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
}]
const sum = (a, b) => Number(a) + Number(b)
const cars = arr.filter(x => x.type === 'Car')
const planes = arr.filter(x => x.type === 'Plane')
const carsValue = cars.map(x => x.value).reduce(sum)
const planesValue = planes.map(x => x.value).reduce(sum)
const carsAnotherValue = cars.map(x => x.another_value).reduce(sum)
const planesAnotherValue = planes.map(x => x.another_value).reduce(sum)

You could first group all the objects by type, and then reduce all the value and another_value:
var types = _.groupBy(array, 'type');
var result = _.mapValues(types, function(value, key) {
return _.reduce(value, function(memo, v) {
return memo + +v.value + +v.another_value;
}, 0);
});
var array = [{
"id": "1",
"type": "Plane",
"date": "date",
"value": "10",
"another_value": "10"
},
{
"id": "1",
"type": "Plane",
"date": "date",
"value": "15",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
}];
var types = _.groupBy(array, 'type');
var result = _.mapValues(types, function(value, key) {
return _.reduce(value, function(memo, v) {
return memo + +v.value + +v.another_value;
}, 0);
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

Another approach
var count = _.countBy(data, 'type');
var sum = _.reduce(data, function(total, obj) {
total[obj.type] += Number(obj.value);
return total;
}, {
Car: 0,
Plane: 0
});
var data = [{
"id": "1",
"type": "Plane",
"date": "date",
"value": "10",
"another_value": "10"
},
{
"id": "1",
"type": "Plane",
"date": "date",
"value": "15",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
},
{
"id": "1",
"type": "Car",
"date": "date",
"value": "25",
"another_value": "10"
}];
var count = _.countBy(data, 'type');
var sum = _.reduce(data, function(total, obj) {
total[obj.type] += Number(obj.value);
return total;
}, {
Car: 0,
Plane: 0
});
console.log(count['Plane']);
console.log(count['Car']);
console.log(sum['Plane']);
console.log(sum['Car']);
<script src="https://cdn.jsdelivr.net/lodash/4.15.0/lodash.min.js"></script>

Related

How to filter and access child element from nested array of objects in Javascript

I have the following array of objects and I would like to get the innermost child attribute if it matches the outer condition:
My condition should be if objArray.displayValue matches "display2" and its types.displayValue matches "inner_display4" then return its value, which is 4.
objArray = [
{
"name": "abc",
"displayValue": "display1",
"types": [
{
"name": "name1",
"displayValue": "inner_display1",
"value": "1"
},
{
"name": "name2",
"displayValue": "inner_display2",
"value": "2"
},
{
"name": "name3",
"displayValue": "inner_display3",
"value": "3"
}
]
},
{
"name": "cdf",
"displayValue": "display2",
"types": [
{
"name": "name4",
"displayValue": "inner_display4",
"value": "4"
},
{
"name": "name5",
"displayValue": "inner_display5",
"value": "5"
},
{
"name": "name6",
"displayValue": "inner_display6",
"value": "6"
}
]
}
]
You can try something like this
const find = (data, displayValue, innerDisplayValue) =>
data.reduce((res, item) => {
if(res || item.displayValue !== displayValue) return res
const type = item.types.find(t => t.displayValue === innerDisplayValue)
return type? type.value: res
}, undefined)
const objArray = [{
"name": "abc",
"displayValue": "display1",
"types": [{
"name": "name1",
"displayValue": "inner_display1",
"value": "1"
}, {
"name": "name2",
"displayValue": "inner_display2",
"value": "2"
}, {
"name": "name3",
"displayValue": "inner_display3",
"value": "3"
}]
}, {
"name": "cdf",
"displayValue": "display2",
"types": [{
"name": "name4",
"displayValue": "inner_display4",
"value": "4"
}, {
"name": "name5",
"displayValue": "inner_display5",
"value": "5"
}, {
"name": "name6",
"displayValue": "inner_display6",
"value": "6"
}]
}]
console.log(find(objArray, 'display2', 'inner_display4'))
console.log(find(objArray, 'display2', 'inner_display40'))
You could try the following:
First find the object that has the correct displayValue and then filter the types to return the object you're looking for. Once you have the correct types object destructure the value.
The const displayValue is your result
Hope the code is a little bit clear :)
const findByDisplayValue = (dv) => objArray.find((obj) => obj.displayValue === dv);
const { types } = findByDisplayValue('display2');
const getType = (typeValue) => types.filter((type) => type.displayValue === typeValue);
const [{ displayValue }] = getType('inner_display4');

Objects nested in objects iteration?

I want to write a function that will find the Categoy[3].options[3].label and matches it to the letter "D".
How do I iterate through the nested objects below to go through the TagCategory.options and search for the option that matches to the letter "D"? If it matches the letter "D", it should return true.
"Category":
[
{
"field": "A",
"options": [
{"tag": "100", "value": "yes"}
],
"label": "Red"
},
{
"field": "tanks",
"type": true,
"options": [
{"tag_value": "4", "value": "4", "label": "A"},
{"tag_value": "3", "value": "3", "label": "B"},
{"tag_value": "2", "value": "2", "label": "C"},
{"tag_value": "1", "value": "1", "label": "D"},
{"tag_value": "5", "value": "5", "label": "E"}
],
"label": "Tanks"
}
]
Something like this?
function findLabel(arr, label) {
for (var i in arr) {
const options = arr[i].options;
const find = options.find(o => o.label == label);
if (find) {
return true;
}
}
return false;
}
const test = findLabel(Category, "D");
Category is your array
function check(category, letter){
var obj;
var optionObj;
for(var i = 0; i < category.length; i++){
obj = Category[i];
options = obj.options;
for(var j = 0; j < options.length; j++){
optionObj = option[j];
if(optionObj.label === letter) return true;
}
}
return false;
}
Here is a rough example of what I mean by a nested for loop.
category = {...}; // your object above
for (i in category) {
field = category[i];
for (option_i in field.options) {
// This is the nested for loop - called thus because it's inside one.
if (field.options[option_i].label == "D"){
return true;
}
}
}
You can use Array.prototype.some()
let optionHasLabelDBool = obj.Category.some(({options}) =>
options.some(({label}) => label === "D"));
let obj = {
"Category":
[{
"field": "A",
"options": [{
"tag": "100",
"value": "yes"
}],
"label": "Red"
},
{
"field": "tanks",
"type": true,
"options": [{
"tag_value": "4",
"value": "4",
"label": "A"
}, {
"tag_value": "3",
"value": "3",
"label": "B"
}, {
"tag_value": "2",
"value": "2",
"label": "C"
}, {
"tag_value": "1",
"value": "1",
"label": "D"
}, {
"tag_value": "5",
"value": "5",
"label": "E"
}],
"label": "Tanks"
}
]
};
let optionHasLabelD = obj.Category.some(({options}) => options.some(({label}) => label === "D"));
console.log(optionHasLabelD);
Using a function, without arrow function or object destructing
function checkOption(array, prop, key, value) {
return array.some(function(obj) {
return obj[prop].some(function(match) {
return match[key] === value;
});
});
}
let optionHasLabelDBool = checkOption(obj.Category, "options", "label", "D");
let obj = {
"Category": [{
"field": "A",
"options": [{
"tag": "100",
"value": "yes"
}],
"label": "Red"
},
{
"field": "tanks",
"type": true,
"options": [{
"tag_value": "4",
"value": "4",
"label": "A"
}, {
"tag_value": "3",
"value": "3",
"label": "B"
}, {
"tag_value": "2",
"value": "2",
"label": "C"
}, {
"tag_value": "1",
"value": "1",
"label": "D"
}, {
"tag_value": "5",
"value": "5",
"label": "E"
}],
"label": "Tanks"
}
]
};
function checkOption(array, prop, key, value) {
return array.some(function(obj) {
return obj[prop].some(function(match) {
console.log(match[key], value, match[key] === value);
return match[key] === value;
});
});
}
let optionHasLabelDBool = checkOption(obj.Category, "options", "label", "D");
console.log(optionHasLabelDBool);

Javascript count between 2 arrays

I have 2 arrays objects and I need to do a count of home many types of card we have.
The first object contains all the car id's and the second list contains the types of cars.
Here is the data:
var arr = {
"categories": [{
"id": "100",
"name": "category name",
"car_id": "1"
}, {
"id": "192",
"name": "category name here",
"car_id": "25"
}, {
"id": "192",
"name": "category name here",
"car_id": "27"
}]
};
var arr2 = {
"cars": [{
"id": "1",
"name": "car name",
"car_id": "1",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "25",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "27",
"type": "fiat"
}]
};
There's only 5 types of cars so I have 5 variables:
var:
ford,
fiat,
mazda,
mini,
mg
So, what I need to end up with is something like this:
ford: 2;
fiat: 1;
mazda: 0;
mini: 0;
mg: 0;
How can I do this?
If your number of types are fixed, then try this approach
Make an map first
var map = {
ford: 0,
fiat: 0,
mazda: 0,
mini: 0,
mg: 0
};
Now iterate the arrays and count by types
arr2.cars.forEach( function( item ){
map[ item.type ]++;
});
your map is populated with the values now.
var arr2 = {
"cars": [{
"id": "1",
"name": "car name",
"car_id": "1",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "25",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "27",
"type": "fiat"
}]
};
var map = {
ford: 0,
fiat: 0,
mazda: 0,
mini: 0,
mg: 0
};
arr2.cars.forEach(function(item) {
map[item.type] ++;
});
console.log(map);
var arr = {
"categories": [{
"id": "100",
"name": "category name",
"car_id": "1"
}, {
"id": "192",
"name": "category name here",
"car_id": "25"
}, {
"id": "192",
"name": "category name here",
"car_id": "27"
}]
};
var arr2 = {
"cars": [{
"id": "1",
"name": "car name",
"car_id": "1",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "25",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "27",
"type": "fiat"
}]
};
var carCount, typeCount;
arr.categories.forEach(function(item){
if(item.hasOwnProperty("car_id")){
carCount = arr.categories.length;
}
});
arr2.cars.forEach(function(item){
if(item.hasOwnProperty("type")){
typeCount = arr2.cars.length;
}
});
console.log(carCount);
console.log(typeCount);
https://jsfiddle.net/Law7rzc2/
All you need is
countBy(arr2.cars, 'type')
The implementation of countBy is left as an exercise.
Array.prototype.reduce is your go-to for this kind of array computation. A Map will help you keep track of unique car makes as you iterate thru the array of cars.
var obj2 = {
"cars": [{
"id": "1",
"name": "car name",
"car_id": "1",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "25",
"type": "ford"
}, {
"id": "4",
"name": "name 2",
"car_id": "27",
"type": "fiat"
}]
};
const typeStat = cars => {
let map = cars.reduce((m, {type}) =>
m.set(type, (m.get(type) || 0) + 1), new Map());
return Array.from(map, ([make, count]) => ({make, count}));
};
let stats = typeStat(obj2.cars)
console.log(stats);
Output
[
{
"make": "ford",
"count": 2
},
{
"make": "fiat",
"count": 1
}
]

Why does concurrent flattener returns only 2 inner most children

I can't figure out why this tree 'flattener' is returning only innermost children, the expectation is that it should return flattened tree.
var x = {
"Fields": {
"Id": "1",
"MasterAccountId": "",
"ParentAccountId": "",
"Name": "Name 1"
},
"Children": [{
"Fields": {
"Id": "2",
"MasterAccountId": "1",
"ParentAccountId": "1",
"Name": "Name 2"
},
"Children": [{
"Fields": {
"Id": "5",
"MasterAccountId": "1",
"ParentAccountId": "2",
"Name": "Name 5"
},
"Children": [{
"Fields": {
"Id": "6",
"MasterAccountId": "1",
"ParentAccountId": "5",
"Name": "Name 6"
}
}, {
"Fields": {
"Id": "7",
"MasterAccountId": "1",
"ParentAccountId": "5",
"Name": "Name 7"
}
}]
}]
}]
}
function recurs(n) {
console.log(n.Fields.Name);
return (n.Children != undefined ? $.map(n.Children, recurs) : n);
}
var r = recurs(x);
It returns elements with id 6, 7, while console.logs all 5 of them.
http://plnkr.co/edit/LdHiR86EDBnZFAh6aAlG?p=preview
Your function only returns n if n.Children is undefined. Since you want a flat array with all the objects you have to build one.
function recurs(n) {
var out = [];
out.push(n.Fields);
if (n.Children) {
for (var i=0, c; c = n.Children[i]; i++) {
out.push.apply(out, recurs(c));
}
}
return out;
}

Recursive iteration over dynamically nested object array

I am using angular JS and one of their examples:http://jsfiddle.net/furf/EJGHX/
I need to take the data when the update function occurs and add some values to it before I send to the server. (If doing this with angular instead of js would be better let me know)
I'm trying to get the 'parentid' and the 'index' and update the children.
Here is the data I'm looping through
{
"children": [{
"id": "5",
"parentid": "0",
"text": "Device Guides",
"index": "1",
"children": [{
"id": "10",
"index": "0",
"text": "Grandstream GXP-21XX"
}, {
"id": "11",
"index": "1",
"text": "Polycom Soundstation/Soundpoint"
}, {
"id": "23",
"index": "2",
"text": "New Polycom"
}]
}, {
"id": "6",
"parentid": "0",
"text": "Pre-Sales Evaluation",
"index": "0",
"children": []
}, {
"id": "7",
"parentid": "0",
"text": "Router Setup Guides",
"index": "2",
"children": [{
"id": "9",
"index": "0",
"text": "Sonicwall"
}, {
"id": "12",
"index": "1",
"text": "Cisco"
}]
}, {
"id": "9",
"parentid": "7",
"text": "Sonicwall",
"index": "0",
"children": []
}, {
"id": "10",
"parentid": "5",
"text": "Grandstream GXP-21XX",
"index": "0",
"children": []
}, {
"id": "11",
"parentid": "5",
"text": "Polycom Soundstation/Soundpoint",
"index": "1",
"children": []
}, {
"id": "12",
"parentid": "7",
"text": "Cisco",
"index": "1",
"children": []
}, {
"id": "15",
"parentid": "0",
"text": "Post-Sales Implementation Check List",
"index": "7",
"children": [{
"id": "16",
"index": "0",
"text": "Porting and New Number Details"
}, {
"id": "18",
"index": "1",
"text": "Partner Setup"
}, {
"id": "19",
"index": "2",
"text": "test"
}, {
"id": "21",
"index": "3",
"text": "test"
}]
}, {
"id": "16",
"parentid": "15",
"text": "Porting and New Number Details",
"index": "0",
"children": []
}, {
"id": "18",
"parentid": "15",
"text": "Partner Setup",
"index": "1",
"children": []
}, {
"id": "19",
"parentid": "15",
"text": "test",
"index": "2",
"children": []
}, {
"id": "20",
"parentid": "0",
"text": "test",
"index": "11",
"children": []
}, {
"id": "21",
"parentid": "15",
"text": "test",
"index": "3",
"children": []
}, {
"id": "23",
"parentid": "5",
"text": "New Polycom",
"index": "2",
"children": []
}, {
"id": "24",
"parentid": "0",
"text": "Test Markup",
"index": "14",
"children": []
}, {
"id": "25",
"parentid": "0",
"text": "test",
"index": "15",
"children": []
}]
}
This is how I'm currently looping through it, but it only gets the first dimension
for (i = 0, l = data.length; i < l; i++) {
parentid = data[i].id == null ? '0' : data[i].id;
data[i].index = i;
if (data[i].children) {
if (data[i].children.length > 0) {
for (q = 0, r = data[i].children.length; q < r; q++) {
data[i].children[q].parentid = parentid;
data[i].children[q].index = q;
}
}
}
}
I found this one on another fiddle, but I don't know how I would grab the parentid or the index
$.each(target.children, function(key, val) { recursiveFunction(key, val) });
function recursiveFunction(key, val) {
actualFunction(key, val);
var value = val['children'];
if (value instanceof Object) {
$.each(value, function(key, val) {
recursiveFunction(key, val)
});
}
}
function actualFunction(key, val) {}
If I'm understanding you correctly, you want each 'child' to have a parentID (defined by its parent; 0 otherwise) and an index (based on its position within it sibling set).
function normalize(parent) {
if (parent && parent.children) {
for (var i = 0, l = parent.children.length; i < l; ++i) {
var child = parent.children[i];
child.index = i;
if (!child.parentId) child.parentId = parent.id || '0';
normalize(child);
}
}
}
normalize(data);
Recursion is calling function inside the same function. Your sample is not a recursion at all;
function runRecursive(input) {
for (var i = 0, l = input.length; i < l; i++) {
var current = input[i];
parentid = current.id == null ? '0' : current.id;
current.index = i;
if (current.children && current.children.length > 0) {
runRecursive(current.children);
};
};
};
runRecursive(data.children);
Also you should define i and l with var keyword, otherwise it will be located in window context and recursion logic will broken.
Though I don't get what is parentid variable for and why it defined outside visible code.

Categories