Combining a collection of objects based on multiple properties - javascript

Is there a Javascipt or a Lodash method to combine this collection of objects:
[
{
typeId: 'random1'
catId: 'random2'
val: '2'
},
{
typeId: 'random1'
catId: 'random2'
val: '3'
},
{
typeId: 'random1'
catId: 'random4'
val: '1'
}
]
into this one, with each object with unique typeId-catId pair and the numerical value to be summed up:
[
{
typeId: 'random1'
catId: 'random2'
val: '5'
},
{
typeId: 'random1'
catId: 'random4'
val: '1'
}
]

First create an object which keeps track of total val of similar objects and then process them into final output format. Something like this:
var data = [
{
typeId: 'random1',
catId: 'random2',
val: '2'
},
{
typeId: 'random1',
catId: 'random2',
val: '3'
},
{
typeId: 'random1',
catId: 'random4',
val: '1'
}
];
var groups = data.reduce(function(acc, obj) {
acc[obj.typeId+'_'+obj.catId] = acc[obj.typeId+'_'+obj.catId] || 0;
acc[obj.typeId+'_'+obj.catId] += +obj.val;
return acc;
}, {});
var result = Object.keys(groups).map(function(key) {
return {
typeId: key.split('_')[0],
catId: key.split('_')[1],
val : groups[key]
}
});
console.log(result);

You can use forEach() loop and thisArg param to group objects by typeId and catId properties.
var arr = [{"typeId":"random1","catId":"random2","val":"2"},{"typeId":"random1","catId":"random2","val":"3"},{"typeId":"random1","catId":"random4","val":"1"}]
var result = [];
arr.forEach(function(e) {
var key = e.typeId + '|' + e.catId
if(!this[key]) this[key] = e, result.push(this[key])
else this[key].val = +this[key].val + +e.val
}, {});
console.log(result)

You could use query-js(*) for this. The module hasn't been maintained for a while though. You would do somthing like this:
arr.groupBy(function(e){return e.typeId + e.catId;}).each(function(grp){
let first = grp.first();
return {
typeId : first.typeId,
catId : first.catId,
val : grp.sum(function(e){return 0 + e.val;})
};
});
It will first group the objects and then do the calculation on each group
(*) Be aware that I'm entirely biased and you can't take my words as a recommendation but only suggestion, since I'm the creator of query-js

What about this one ?
for (var i=0; i<array.length; i++) {
for (var j=i+1; j<array.length; j++) {
if (array[i].typeId == array[j].typeId && array[i].catId == array[j].catId) {
array[i].val = parseInt(array[i].val) + parseInt(array[j].val);
array.splice(j--, 1);
}
}
}

Related

what can i do i got out when i am searching class then count how many student in class

let student = [{
id:1,
name:'aman',
class:'10',
gender:'male'
},{
id:2,
name:'shivani',
class:'10',
gender:'female'
},{
id:2,
name:'riyan',
class:'11',
gender:'female'
}]
function customFilter(objList, text){
if(undefined === text || text === '' ) return objList;
return objList.filter(product => {
let flag;
for(let prop in product){
if(product[prop].toString().indexOf(text)>-1){
product[prop] = 0
product[prop]++
flag = product[prop]
console.log(flag)
}
}
return flag;
});}
console.log( customFilter(student, '10'))
I want the output of the number of students in a class. Example: when I pass class 10 as an argument then i should get output how many students in class 10
output:
{class:10,stduent:5 }
I didn't get your question well, but I assumed you want number of student in a class like this {class:10, students: 2}
let student = [
{ id:1, name:'aman', class:'10', gender:'male'},
{ id:2, name:'shivani', class:'10', gender:'female' },
{ id:3, name:'riyan', class:'11', gender:'female' }
]
function customFilter(objList, text){
if(undefined === text || text === '' ) return objList;
const numberOfStudents = objList.filter(product => {
for (let prop in product) {
if (product[prop].toString().includes(text)) {
return true;
}
}
});
return {class:text, student:numberOfStudents.length }
}
console.log( customFilter(student, '10'))
If that's the case this code will do , hope it helps
This would also work:
let students = [
{ id: 1, name: "aman", class: "10", gender: "male" },
{ id: 2, name: "shivani", class: "10", gender: "female" },
{ id: 2, name: "riyan", class: "11", gender: "female" },
];
const customFilter = (students, key, value) => {
const res = { [key]: value, student: 0 };
students.forEach((student) => {
if (student[key] === value) {
res.student += 1;
}
});
return res;
};
console.log(customFilter(students, "class", "10"));
Using Array.prototype.forEach()
There are few problems with the code. change class:'10' to grade: 10,.
don't use "class" as a variable name. might cause a few errors
There is a missing ,
numbers shouldn't be inside quotes because the number will be stored as a string
let student = [
{ id: 1, name: 'aman', grade: 10, gender: 'male'},
{ id: 2, name: 'shivani', grade: 10, gender: 'female' },
{ id: 2, name: 'riyan', grade: 11, gender: 'female' },
]
function customFilter(objList, value){
if(!value || value === '') return objList;
let count = 0
objList.forEach(obj => {
const { grade } = obj;
if(grade === value){
count++;
}
})
return {grade: 10, count};
}
console.log(customFilter(student, 10));
and forEach can be used instead of filter. It loops from start to end of an array
Use .reduce() to group all objects that match.
/* hits (accumulator) is initially an empty array.
now (current) is the object of the current iteration. */
array.reduce((hits, now) => { //...
On each iteration, get all of the current object's values (in lower case) in an array.
Object.values(now).map(val => val.toLowerCase())
/* result of the first object: ["01gn3z1ryjjqhn588ax3bws6qb", "theo bramstom",
"genderqueer", "english"] */
If any of the values of the current object matches the given string (term), add the current object to the hits array.
if (Object.values(now)
.map(val => val.toLowerCase()).includes(term.toLowerCase())) {
hits.push(now);
}
An object literal is returned.
{
"matches": /* an array of all matched objects */,
"total": /* the .length of "matches" array */
};
/* To get the answer prompted in OP -- do the following */
const x = dataFilter(students, "Math");
console.log(x.total);
// NOTE: key "class" is now "subject" just for aesthetics
const students=[{id:"01GN3Z1RYJJQHN588AX3BWS6QB",name:"Theo Bramstom",gender:"Genderqueer",subject:"English"},{id:"01GN3Z1RYM527HAX56ZN14F0YB",name:"Juli Marsy",gender:"Female",subject:"History"},{id:"01GN3Z1RYPYP1FFFEY55T92VX2",name:"Linc Espley",gender:"Non-binary",subject:"Math"},{id:"01GN3Z1RYR325M0QETVVPE2N5J",name:"Barbabas Grisley",gender:"Male",subject:"Math"},{id:"01GN3Z1RYTXA49SBQYXR9DMC04",name:"Godfree Braybrook",gender:"Male",subject:"English"},{id:"01GN3Z1RYVE4N5D16C8QWB1XGF",name:"Jason De Vuyst",gender:"Male",subject:"History"},{id:"01GN3Z1RYXY9WXF1Y407HXFYH8",name:"Adler McCanny",gender:"Male",subject:"Math"},{id:"01GN3Z1RYY9XV444J0SP5Y0QC2",name:"Noellyn MacMorland",gender:"Genderqueer",subject:"Math"},{id:"01GN3Z1RZ0HPQNZ1VKX8ZHA9ZY",name:"Padget Geldeford",gender:"Male",subject:"Math"},
{id:"01GN3Z1RZ2DZE92NG42KSGDXN9",name:"Milissent Treby",gender:"Female",subject:"Art"}];
const dataFilter = (array, term) => {
let result = array.reduce((hits, now) => {
if (Object.values(now).map(val => val.toLowerCase()).includes(term.toLowerCase())) {
hits.push(now);
}
return hits;
}, []);
return {"matches": result, "total": result.length};
}
console.log(dataFilter(students, "Math"));
// Control case: term === "Math"
console.log(dataFilter(students, "PE"));
// No match case: term != "PE"
console.log(dataFilter(students, "female"));
// Case insensitive case: term === "Female"

Create object from multiple object with same value in map JS [duplicate]

I have an array of objects:
[
{ key : '11', value : '1100', $$hashKey : '00X' },
{ key : '22', value : '2200', $$hashKey : '018' }
];
How do I convert it into the following by JavaScript?
{
"11": "1100",
"22": "2200"
}
Tiny ES6 solution can look like:
var arr = [{key:"11", value:"1100"},{key:"22", value:"2200"}];
var object = arr.reduce(
(obj, item) => Object.assign(obj, { [item.key]: item.value }), {});
console.log(object)
Also, if you use object spread, than it can look like:
var object = arr.reduce((obj, item) => ({...obj, [item.key]: item.value}) ,{});
One more solution that is 99% faster is(tested on jsperf):
var object = arr.reduce((obj, item) => (obj[item.key] = item.value, obj) ,{});
Here we benefit from comma operator, it evaluates all expression before comma and returns a last one(after last comma). So we don't copy obj each time, rather assigning new property to it.
This should do it:
var array = [
{ key: 'k1', value: 'v1' },
{ key: 'k2', value: 'v2' },
{ key: 'k3', value: 'v3' }
];
var mapped = array.map(item => ({ [item.key]: item.value }) );
var newObj = Object.assign({}, ...mapped );
console.log(newObj );
One-liner:
var newObj = Object.assign({}, ...(array.map(item => ({ [item.key]: item.value }) )));
You're probably looking for something like this:
// original
var arr = [
{key : '11', value : '1100', $$hashKey : '00X' },
{key : '22', value : '2200', $$hashKey : '018' }
];
//convert
var result = {};
for (var i = 0; i < arr.length; i++) {
result[arr[i].key] = arr[i].value;
}
console.log(result);
I like the functional approach to achieve this task:
var arr = [{ key:"11", value:"1100" }, { key:"22", value:"2200" }];
var result = arr.reduce(function(obj,item){
obj[item.key] = item.value;
return obj;
}, {});
Note: Last {} is the initial obj value for reduce function, if you won't provide the initial value the first arr element will be used (which is probably undesirable).
https://jsfiddle.net/GreQ/2xa078da/
Using Object.fromEntries:
const array = [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
];
const obj = Object.fromEntries(array.map(item => [item.key, item.value]));
console.log(obj);
A clean way to do this using modern JavaScript is as follows:
const array = [
{ name: "something", value: "something" },
{ name: "somethingElse", value: "something else" },
];
const newObject = Object.assign({}, ...array.map(item => ({ [item.name]: item.value })));
// >> { something: "something", somethingElse: "something else" }
you can merge array of objects in to one object in one line:
const obj = Object.assign({}, ...array);
Use lodash!
const obj = _.keyBy(arrayOfObjects, 'keyName')
Update: The world kept turning. Use a functional approach instead.
Previous answer
Here you go:
var arr = [{ key: "11", value: "1100" }, { key: "22", value: "2200" }];
var result = {};
for (var i=0, len=arr.length; i < len; i++) {
result[arr[i].key] = arr[i].value;
}
console.log(result); // {11: "1000", 22: "2200"}
Simple way using reduce
// Input :
const data = [{key: 'value'}, {otherKey: 'otherValue'}];
data.reduce((prev, curr) => ({...prev, ...curr}) , {});
// Output
{key: 'value', otherKey: 'otherValue'}
More simple Using Object.assign
Object.assign({}, ...array);
Using Underscore.js:
var myArray = [
Object { key="11", value="1100", $$hashKey="00X"},
Object { key="22", value="2200", $$hashKey="018"}
];
var myObj = _.object(_.pluck(myArray, 'key'), _.pluck(myArray, 'value'));
Nearby 2022, I like this approach specially when the array of objects are dynamic which also suggested based on #AdarshMadrecha's test case scenario,
const array = [
{ key : '11', value : '1100', $$hashKey : '00X' },
{ key : '22', value : '2200', $$hashKey : '018' }];
let obj = {};
array.forEach( v => { obj[v.key] = v.value }) //assign to new object
console.log(obj) //{11: '1100', 22: '2200'}
let array = [
{ key: "key1", value: "value1" },
{ key: "key2", value: "value2" },
];
let arr = {};
arr = array.map((event) => ({ ...arr, [event.key]: event.value }));
console.log(arr);
Was did yesterday
// Convert the task data or array to the object for use in the above form
const {clientData} = taskData.reduce((obj, item) => {
// Use the clientData (You can set your own key name) as the key and the
// entire item as the value
obj['clientData'] = item
return obj
}, {});
Here's how to dynamically accept the above as a string and interpolate it into an object:
var stringObject = '[Object { key="11", value="1100", $$hashKey="00X"}, Object { key="22", value="2200", $$hashKey="018"}]';
function interpolateStringObject(stringObject) {
var jsObj = {};
var processedObj = stringObject.split("[Object { ");
processedObj = processedObj[1].split("},");
$.each(processedObj, function (i, v) {
jsObj[v.split("key=")[1].split(",")[0]] = v.split("value=")[1].split(",")[0].replace(/\"/g,'');
});
return jsObj
}
var t = interpolateStringObject(stringObject); //t is the object you want
http://jsfiddle.net/3QKmX/1/
// original
var arr = [{
key: '11',
value: '1100',
$$hashKey: '00X'
},
{
key: '22',
value: '2200',
$$hashKey: '018'
}
];
// My solution
var obj = {};
for (let i = 0; i < arr.length; i++) {
obj[arr[i].key] = arr[i].value;
}
console.log(obj)
You can use the mapKeys lodash function for that. Just one line of code!
Please refer to this complete code sample (copy paste this into repl.it or similar):
import _ from 'lodash';
// or commonjs:
// const _ = require('lodash');
let a = [{ id: 23, title: 'meat' }, { id: 45, title: 'fish' }, { id: 71, title: 'fruit' }]
let b = _.mapKeys(a, 'id');
console.log(b);
// b:
// { '23': { id: 23, title: 'meat' },
// '45': { id: 45, title: 'fish' },
// '71': { id: 71, title: 'fruit' } }

How to invert the structure of nested array of objects in Javascript?

I currently have an array that has the following structure:
data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
But I would like to restructure the array to get something like this:
data = [
{
name: "thing1",
info: [{
time: 100,
count: 3
}, {
time: 1000,
count: 7
}, {
}]
},
{
name: "thing2",
info: [{
time: 100,
count: 2
}, {
time: 1000,
count: 0
}, {
}]
}
];
So basically the key would have to be switched from time to name, but the question is how. From other posts I have gathered that using the map function might work, but since other posts had examples to and from different structures I am still not sure how to use this.
There are a number of ways to achieve this however, the key idea will be to perform a nested looping of both data items and their (nested) info items. Doing that allows your algorithm to "visit" and "map" each piece of input data, to a corresponding value in the resulting array.
One way to express that would be to use nested calls to Array#reduce() to first obtaining a mapping of:
name -> {time,count}
That resulting mapping would then be passed to a call to Object.values() to transform the values of that mapping to the required array.
The inner workings of this mapping process are summarized in the documentation below:
const data=[{time:100,info:[{name:"thing1",count:3},{name:"thing2",count:2},{}]},{time:1e3,info:[{name:"thing1",count:7},{name:"thing2",count:0},{}]}];
const result =
/* Obtain array of values from outerMap reduce result */
Object.values(
/* Iterate array of data items by reduce to obtain mapping of
info.name to { time, count} value type */
data.reduce((outerMap, item) =>
/* Iterate inner info array of current item to compound
mapping of info.name to { time, count} value types */
item.info.reduce((innerMap, infoItem) => {
if(!infoItem.name) {
return innerMap
}
/* Fetch or insert new { name, info } value for result
array */
const nameInfo = innerMap[ infoItem.name ] || {
name : infoItem.name, info : []
};
/* Add { time, count } value to info array of current
{ name, info } item */
nameInfo.info.push({ count : infoItem.count, time : item.time })
/* Compound updated nameInfo into outer mapping */
return { ...innerMap, [ infoItem.name] : nameInfo }
}, outerMap),
{})
)
console.log(result)
Hope that helps!
The approach I would take would be to use an intermediate mapping object and then create the new array from that.
const data = [{time: 100, info: [{name: "thing1", count: 3}, {name: "thing2", count: 2}, {}]}, {time: 1e3, info: [{name: "thing1", count: 7}, {name: "thing2", count: 0}, {}]} ];
const infoByName = {};
// first loop through and add entries based on the name
// in the info list of each data entry. If any info entry
// is empty ignore it
data.forEach(entry => {
if (entry.info) {
entry.info.forEach(info => {
if (info.name !== undefined) {
if (!infoByName[info.name]) {
infoByName[info.name] = [];
}
infoByName[info.name].push({
time: entry.time,
count: info.count
});
}
});
}
});
// Now build the resulting list, where name is entry
// identifier
const keys = Object.keys(infoByName);
const newData = keys.map(key => {
return {
name: key,
info: infoByName[key]
};
})
// newData is the resulting list
console.log(newData);
Well, the other guy posted a much more elegant solution, but I ground this one out, so I figured may as well post it. :)
var data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
var newArr = [];
const objInArray = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o)
return true;
}
return false;
}
const getIndex = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o) {
return i;
}
}
return false;
}
const getInfoObj = (t, c) => {
let tmpObj = {};
tmpObj.count = c;
tmpObj.time = t;
return tmpObj;
}
for (var i=0; i < data.length; i += 1) {
let t = data[i].time;
for (var p in data[i].info) {
if ("name" in data[i].info[p]) {
if (objInArray(data[i].info[p].name, newArr)) {
let idx = getIndex(data[i].info[p].name, newArr);
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newArr[idx].info.push(newInfoObj);
} else {
let newObj = {};
newObj.name = data[i].info[p].name;
let newInfo = [];
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newInfo.push(newInfoObj);
newObj.info = newInfo;
newArr.push(newObj);
}}
}
}
console.log(newArr);
try to use Object.keys() to get the key

retriving values from javascript object and then convert it to one object

I have a problem! I am creating an rating app, and I have come across a problem that I don't know how to solve. The app is react native based so I am using JavaScript.
The problem is that I have multiple objects that are almost the same, I want to take out the average value from the values of the "same" objects and create a new one with the average value as the new value of the newly created object
This array in my code comes as a parameter to a function
var arr = [
{"name":"foo","value":2},
{"name":"foo","value":5},
{"name":"foo","value":2},
{"name":"bar","value":2},
{"name":"bar","value":1}
]
and the result I want is
var newArr = [
{"name":"foo","value":3},
{"name":"bar","value":1.5},
]
If anyone can help me I would appreciate that so much!
this is not my exact code of course so that others can take help from this as well, if you want my code to help me I can send it if that's needed
If you have any questions I'm more than happy to answer those
Iterate the array with Array.reduce(), and collect to object using the name values as the key. Sum the Value attribute of each name to total, and increment count.
Convert the object back to array using Object.values(). Iterate the new array with Array.map(), and get the average value by dividing the total by count:
const arr = [{"name":"foo","Value":2},{"name":"foo","Value":5},{"name":"foo","Value":2},{"name":"bar","Value":2},{"name":"bar","Value":1}];
const result = Object.values(arr.reduce((r, { name, Value }) => {
if(!r[name]) r[name] = { name, total: 0, count: 0 };
r[name].total += Value;
r[name].count += 1;
return r;
}, Object.create(null)))
.map(({ name, total, count }) => ({
name,
value: total / count
}));
console.log(result);
I guess you need something like this :
let arr = [
{name: "foo", Value: 2},
{name: "foo", Value: 5},
{name: "foo", Value: 2},
{name: "bar", Value: 2},
{name: "bar", Value: 1}
];
let tempArr = [];
arr.map((e, i) => {
tempArr[e.name] = tempArr[e.name] || [];
tempArr[e.name].push(e.Value);
});
var newArr = [];
$.each(Object.keys(tempArr), (i, e) => {
let sum = tempArr[e].reduce((pv, cv) => pv+cv, 0);
newArr.push({name: e, value: sum/tempArr[e].length});
});
console.log(newArr);
Good luck !
If you have the option of using underscore.js, the problem becomes simple:
group the objects in arr by name
for each group calculate the average of items by reducing to the sum of their values and dividing by group length
map each group to a single object containing the name and the average
var arr = [
obj = {
name: "foo",
Value: 2
},
obj = {
name: "foo",
Value: 5
},
obj = {
name: "foo",
Value: 2
},
obj = {
name: "bar",
Value: 2
},
obj = {
name: "bar",
Value: 1
}
]
// chain the sequence of operations
var result = _.chain(arr)
// group the array by name
.groupBy('name')
// process each group
.map(function(group, name) {
// calculate the average of items in the group
var avg = (group.length > 0) ? _.reduce(group, function(sum, item) { return sum + item.Value }, 0) / group.length : 0;
return {
name: name,
value: avg
}
})
.value();
console.log(result);
<script src="http://underscorejs.org/underscore-min.js"></script>
In arr you have the property Value and in newArr you have the property value, so I‘ll assume it to be value both. Please change if wished otherwise.
var map = {};
for(i = 0; i < arr.length; i++)
{
if(typeof map[arr[i].name] == ‘undefined‘)
{
map[arr[i].name] = {
name: arr[i].name,
value: arr[i].value,
count: 1,
};
} else {
map[arr[i].name].value += arr[i].value;
map[arr[i].name].count++;
}
var newArr = [];
for(prop in map)
{
map[prop].value /= map[prop].count;
newArr.push({
name: prop,
value: map[prop].value
});
}
delete map;

Nested grouping with javascript (ES5)

I have an array of objects as following :
[
{"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
{"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
...
{"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
]
I want to group this array by (moduleID,moduleTitle) and then by (categoryID,categoryTitle).
This is what I tried :
function groupBy(data, id, text) {
return data.reduce(function (rv, x) {
var el = rv.find(function(r){
return r && r.id === x[id];
});
if (el) {
el.children.push(x);
} else {
rv.push({ id: x[id], text: x[text], children: [x] });
}
return rv;
}, []);
}
var result = groupBy(response, "moduleID", "moduleTitle");
result.forEach(function(el){
el.children = groupBy(el.children, "categoryID", "categoryTitle");
});
The above code is working as expected, but as you can see, after the first grouping I had to iterate again over the array which was grouped by the moduleId in order to group by the categoryId.
How can I modify this code so I can only call groupBy function once on the array ?
Edit:
Sorry this might be late, but I want this done by using ES5, no Shim and no Polyfill too.
Here's one possible (although may be a bit advanced) approach:
class DefaultMap extends Map {
constructor(factory, iter) {
super(iter || []);
this.factory = factory;
}
get(key) {
if (!this.has(key))
this.set(key, this.factory());
return super.get(key);
}
}
Basically, it's the a Map that invokes a factory function when a value is missing. Now, the funny part:
let grouper = new DefaultMap(() => new DefaultMap(Array));
for (let item of yourArray) {
let firstKey = item.whatever;
let secondKey = item.somethingElse;
grouper.get(firstKey).get(secondKey).push(item);
}
For each firstKey this creates a Map inside grouper, and the values of those maps are arrays grouped by the second key.
A more interesting part of your question is that you're using compound keys, which is quite tricky in JS, since it provides (almost) no immutable data structures. Consider:
items = [
{a: 'one', b: 1},
{a: 'one', b: 1},
{a: 'one', b: 2},
{a: 'two', b: 2},
]
let grouper = new DefaultMap(Array);
for (let x of items) {
let key = [x.a, x.b]; // wrong!
grouper.get(key).push(x);
}
So, we're naively grouping objects by a compound key and expecting to see two objects under ['one', 1] in our grouper (which is one level for the sake of the example). Of course, that won't work, because each key is a freshly created array and all of them are different for Map or any other keyed storage.
One possible solution is to create an immutable structure for each key. An obvious choice would be to use Symbol, e.g.
let tuple = (...args) => Symbol.for(JSON.stringify(args))
and then
for (let x of items) {
let key = tuple(x.a, x.b); // works
grouper.get(key).push(x);
}
You could extend your function by using an array for the grouping id/names.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
groups.reduce(function (level, key) {
var el;
level.some(function (r) {
if (r && r.id === x[key[0]]) {
el = r;
return true;
}
});
if (!el) {
el = { id: x[key[0]], text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv).push({ id: x.id, text: x.lib });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Version with path as id.
function groupBy(data, groups) {
return data.reduce(function (rv, x) {
var path = [];
var last = groups.reduce(function (level, key, i) {
path.length = i;
path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];
var id = path.join(';'),
el = level.find(function (r) {
return r && r.id === id;
});
if (!el) {
el = { id: path.join(';'), text: x[key[1]], children: [] };
level.push(el);
}
return el.children;
}, rv);
last.push({ id: path.concat('NODE:' + x.id).join(';') });
return rv;
}, []);
}
var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];
var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do it like this:
const exit = Symbol("exit");
function groupBy(arr, ...props){
const root = {};
for(const el of arr){
const obj = props.map(key => el[key])
.reduce((obj, key) => obj[key] || (obj[key] = {}), root);
(obj[exit] || (obj[exit] = [])).push(el);
}
}
So you can access it like:
const grouped = groupBy(response, "moduleID", "moduleTitle");
console.log( grouped[2]["workflow"][exit] );
You might leave away that exit symbol, but it feels a bit wrong to mix a nested tree with arrays.

Categories