Looking to group array by values in the sub array - javascript

Trying to parse one data set that has a bunch of the same "secondaryIDs" in way that i can group and iterate through them together.
In english what im trying to do is
"select a unique group of all items where the value of field is unique "
'use strict';
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
let unique = [...new Set(data.map(item => item.Group))];
console.log(unique);
Which gives ["A"],["B"]
but what im looking for is
{
A: [ "SD","MM" ],
B: [ "FI","CO" ],
}

For this, I would use array.reduce instead of array.map because what you're actually hoping to return is a new value, not a modified array, the reduce method is perfect when you want to literally reduce the array into a single output value, in your case an object of unique groups. Maybe try something like this:
let unique = data.reduce((acc, { Group, Name }) => {
if (!(acc.hasOwnProperty(Group))) {
acc[Group] = [Name];
} else {
acc[Group].push(Name);
};
return acc;
}, {});
I've also added a pen for this at: https://codepen.io/anon/pen/BGpgdz?editors=1011 so you can see this working.
Hope this helps!

You can also reduce your array to the grouped object (keyed by Group values):
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const grouped = data.reduce((a, {Group, Name}) => {
if (!(Group in a)) a[Group] = [Name];
else a[Group].push(Name);
return a;
}, {});
console.log(grouped);

can do something like..
const map = {};
data.forEach( d => {
if( map[d.Group] ) {
map[d.Group].push(d.Name);
} else {
map[d.Group] = [d.Name];
}
})
console.log(map)

I think the easiest way to achieve this would be to use Array.prototype.reduce method to create an object that maps unique Group names to arrays that contain Names. You can supply an empty object literal as your initial reduce accumulator:
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
var namesByGroup = data.reduce((map, el) => {
map[el.Group] ? map[el.Group].push(el.Name) : map[el.Group] = [el.Name];
return map;
}, {});
console.log(namesByGroup);

If you're interested in a functional approach, here is a solution using Ramda:
const group =
R.pipe(
R.groupBy(R.prop('Group')),
R.map(R.map(R.prop('Name'))));
console.log(
group([
{Group: 'A', Name: 'SD'},
{Group: 'B', Name: 'FI'},
{Group: 'A', Name: 'MM'},
{Group: 'B', Name: 'CO'}])
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

can also be done using forEach
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const somefunction = (data) => {
let arr = {}
data.forEach( ({Group, Name}) => {
Group in arr ? arr[Group].push(Name) : arr[Group] = [Name]
})
return arr;
}
console.log(somefunction(data))

Related

How to iteratively nest objects within an object

I have an array that looks like this:
const arr = [
{
parent: 'A',
children: ['B'],
},
{
parent: 'B',
children: ['C'],
},
{
parent: 'C',
children: ['D']
}];
and I want to create a function that will take this array and result in the following object:
const result = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
so the result type would look like:
type Result = {
parent: string;
children: Result[];
};
What I've tried so far:
type TInput = {
parent: string;
children: string[];
};
type Result = {
parent: string;
children: Result[];
};
// can assume we know initial parent is 'A'
const fn = (parent: string, inputArr: TInput[]) => {
const result: TResult[] = [];
let newParent: string[] = [];
while (newParent.length !== 0) {
const index = inputArr.findIndex(
(input) => input.parent === parent
);
result.push({
parent: inputArr[index].parent,
children: [], // need to populate on next pass?
});
newParent = inputArr[index].children;
}
return result;
};
I don't know how many objects will be in the input array, but can assume first object is known to be initial parent/child ('A' in the example). Any help much appreciated.
Thanks
I'd use a parent map to recreate children attribute as arrays of parent objects:
const arr = [
{
parent: 'A',
children: ['B'],
},
{
parent: 'B',
children: ['C'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'D',
children: []
}
];
const makeTree = (manyParents,rootName) => {
// copy parent objects into a map.
let mapIt = new Map(manyParents.map(pObject => {
return [pObject.parent, pObject];
}));
// recreate children arrays for each parents.
mapIt.forEach((oneParent) => {
let newChildrenArray = [];
//find every children objects.
oneParent.children.forEach((oneChild) => {
newChildrenArray.push(mapIt.get(oneChild));
});
//replace children array.
oneParent.children = newChildrenArray;
});
return mapIt.get(rootName);
}
let tree = makeTree(arr,'A');
console.log(tree)
You can use Array#reverse and Array#reduce methods as follows:
const
arr = [ { parent: 'A', children: ['B'], }, { parent: 'B', children: ['C'], }, { parent: 'C', children: ['D'] }],
output = arr.reverse().reduce(
(acc,{parent,children}) =>
!acc.parent ?
({parent,children:children.map( ([parent]) => ({parent,children:[]}) )}):
({parent,children:[acc]}),{}
);
console.log( output );
This seems to do the job :
This is the ts version :
// here i just copy your data
const data = [{
parent: 'A',
children: ['B'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'B',
children: ['C'],
}
];
const expectedResult = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
type TInput = {
parent: string;
children: string[];
};
type TResult = {
parent: string;
children: TResult[];
};
// there is the function that takes an input (the parent element) and an array (all the children)
const parseArray = (obj: TInput, arr: TInput[]): TResult => {
return {
parent: obj.parent,
// if the children exists on the array, we use it, else we create an empty one, which will be used to recusivly generate the tree
children: obj.children.map(child => data.find(e => e.parent === child) ?? {
parent: child,
children: []
}).map(e => parseArray(e, arr))
}
}
// we get the root obj (as you said the first el)
const root = data.shift()!
// and we call the function
console.log(parseArray(root, data))
// we verify that the objects are the same using json (directly using == on objects compares their locations on the disk)
console.log(JSON.stringify(parseArray(root, data)) === JSON.stringify(expectedResult))
And this is the snipped (we can't run ts directly on snippets) :
// here i just copy your data
const data = [{
parent: 'A',
children: ['B'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'B',
children: ['C'],
}
];
const expectedResult = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
// there is the function that takes an input (the parent element) and an array (all the children)
const parseArray = (obj, arr) => {
return {
parent: obj.parent,
// if the children exists on the array, we use it, else we create an empty one, which will be used to recusivly generate the tree
children: obj.children.map(child => data.find(e => e.parent === child) ?? {
parent: child,
children: []
}).map(e => parseArray(e, arr))
}
}
// we get the root obj (as you said the first el)
const root = data.shift()
// and we call the function
console.log(parseArray(root, data))
// we verify that the objects are the same using json (directly using == on objects compares their locations on the disk)
console.log(JSON.stringify(parseArray(root, data)) === JSON.stringify(expectedResult))

Can we get an object from single object key value in javascript?

I have an array of object - like this -
test: [
{
id:'1',
name:'A'
},
{
id:'2',
name:'B'
},
]
Suppose I have a value 2 that exists in object Test as id. I want to get whole object from array if id value exists in whole array
input - 2,
expected output - {id:'2' , name:'B'}
How Can we get it ? is it any possible solution ?
Simply use find-
const val = [
{
id: '1',
name: 'A',
},
{
id: '2',
name: 'B',
},
];
const res = val.find(obj => obj.id === '2');
console.log(res);
There can be multiple ways to do this. Here is how I did it.
let test = [
{
id: '1',
name: 'A'
},
{
id: '2',
name: 'B'
}
];
let result = (param) => test.filter(el => {
return el.id == param
});
console.log(result(2))

Get count from Array of arrays

I have an array of arrays below. With ES6, how can I get a count of each value Good, Excellent & Wow into a new array e.g [{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}] in dynamic style. I am attempting to use Object.assign but I am failing to "unique" out the count of the key plus instead, I need to use an array as I am trying to render this out on the front end. Do I need to use reduce? how?
let k = 0
const stats = {}
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
]
remarks.forEach((arr) => {
arr.map((e) => {
Object.assign(stats, { [e.name]: k = k + 1 })
})
})
console.log(stats);
Output:
stats: {Good: 8, Excellent: 11, Wow: 9}
Which is Incorrect plus I need to use an array.
Expected output:
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
Flatten the array of arrays and reduce it starting with an object like : { Good: 0, Excellent: 0, Wow: 0}
then .map the Object.entries of the result to transform it to an array :
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const result = Object.entries(
remarks.flat().reduce(
(all, { name }) => {
all[name] += 1;
return all;
},
{ Good: 0, Excellent: 0, Wow: 0 }
)
).map(([name, count]) => ({ name, count }));
console.log(result);
You can try below logic:
var data = [[{name: "Good"}],[{name: "Good"}, {name:"Excellent"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name:"Excellent"}],[{name:"Excellent"}]]
var nData = [];
(data || []).forEach( e => {
(e || []).forEach(ei => {
var i = (index = nData.findIndex(d => d.name === ei.name)) >=0 ? index : nData.length;
nData[i] = {
name: ei.name,
count : (nData[i] && nData[i].count ? nData[i].count : 0)+1
}
});
});
console.log(nData);
Hope this helps!
You can use reduce, then convert the result into an array of objects:
const counts = remarks.reduce((result, list) => {
list.forEach(remark => {
result[remark.name] = (result[remark.name] || 0) + 1;
});
}, {});
const finalResult = [];
for (let name in counts) {
finalResult.push({name, count: counts[name]});
}
You could achieve this pretty easily by:
1) Flattening the nested array into 1 single level array.
2) Iterating over the flat array and create a "count map" by using Array.prototype.reduce
For example:
const remarks = [
[{
name: 'Good'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Excellent'
}],
[{
name: 'Excellent'
}]
]
const flatten = arr => arr.reduce((accum, el) => accum.concat(el), [])
const map = flatten(remarks).reduce((accum, el) => {
if (accum[el.name]) {
accum[el.name] += 1;
} else {
accum[el.name] = 1;
}
return accum;
}, {});
console.log(map)
First find the counts using reduce than pass that to another function to get the desired view structure:
const Good = 1,
Excellent = 2,
Wow = 3;
const remarks = [
[{name: Good}],
[{name: Good}, {name:Excellent}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name:Excellent}],
[{name:Excellent}]
];
/*
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
*/
function counts(remarks) {
return remarks.flat().reduce((acc, v) => {
const name = v.name;
let count = acc[name] || 0;
return {
...acc,
[name]: count + 1
}
}, {});
}
function view(counts) {
return Object.keys(counts).map(key => {
let count = counts[key];
return { name: key, count };
})
}
console.log(view(counts(remarks)));
Any time you are making a smaller set of data, or transforming data, in JavaScript reduce should be the first method you attempt to use. In this case, you may want to pair it with an indexer (hence preloading with an array of index and an array of result).
This works in one pass without needing to know the name values up front.
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
];
const stats = remarks.reduce((p,c) => (
c.forEach( ({name}) => {
if(!p[0].hasOwnProperty(name)){
p[1].push({name:name,count:0});
p[0][name] = p[1].length - 1;
}
p[1][p[0][name]].count++;
}),p),[{},[]])[1];
console.log(stats);
A slightly more concise and definitely less readable approach (but it's worth to mention) could be:
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const stats = Object.entries(
remarks
.flat()
.reduce((acc, {name}) => (acc[name] = -~acc[name], acc), {})))
).map(([name, count]) => ({ name, count }));
console.log(stats);
It uses the comma operator in the reducer to returns the accumulator; and the bitwise operator NOT to create a counter without the needs to initialize the object upfront with all the names.
const flattenedRemarks = _.flatten(remarks);
const groupedRemarks = _.groupBy(flattenedRemarks, (remark) => remark.name);
const remarkCounts = _.mapValues(groupedRemarks, (group) => group.length);
const data = {
"mchale": {
"classes":["ESJ030", "SCI339"], // get the length
"faculty":["Hardy", "Vikrum"] // get the length
},
"lawerence":{
"classes":["ENG001"], // get the length
"faculty":["Speedman", "Lee", "Lazenhower"] // get the length
}
};
const count = Object.keys(data).map(campusName => {
const campus = data[campusName];
return Object.keys(campus).map(key => campus[key].length).reduce((p, c) => p + c, 0);
}).reduce((p, c) => p + c, 0);
console.log(count);

Dynamic solution to group by an array of objects

I'm trying to do a group by over an array of objects. The array that I'm trying to group by is subject to change and I need a solution that's dynamic.
This is how the array that i'm trying to work on looks like.
const arr = [
{
first: {
label: 'a',
key: 'a'
},
second: {
label: 'b',
key: 'b',
}
},
{
first: {
label: 'aa',
key: 'aa'
},
second: {
label: 'bb',
key: 'bb',
}
}
]
I've tried this so far:
const result = arr.reduce((acc, curr) => {
acc['first'] = acc['first'] || [];
acc['second'] = acc['second'] || [];
acc['first'].push(curr.first);
acc['second'].push(curr.second);
return acc;
}, {});
This solves my problem, but it's not a dynamic solution.
This is the expected result:
const obj = {
first: [
{
label: 'a',
key: 'a'
},
{
label: 'aa',
key: 'aa'
}
],
second: [
{
label: 'b',
key: 'b'
},
{
label: 'bb',
key: 'bb'
}
]
}
To make this more general, you simply need to loop over the keys, rather than hardcoding your first/second code. That should look something like this:
const result = arr.reduce((acc, curr) => {
let keys = Object.keys(curr);
keys.forEach((key) => {
acc[key] = acc[key] || [];
acc[key].push(curr[key]);
});
return acc;
}, {});
You can use reduce and Object.entries
const arr = [{first: {label: 'a',key: 'a'},second: {label: 'b',key: 'b',}},{first: {label: 'aa',key: 'aa'},second: {label: 'bb',key: 'bb',}}]
let final = arr.reduce((op, inp) => {
Object.entries(inp).forEach(([key, value]) => {
op[key] = op[key] || []
op[key].push(value)
})
return op
},{})
console.log(final)

group array by groups and sort by position

I have an array. I need to group this array by groups and sort by position. I tied to create a new array with group names as keys and values as sorted array grouped by group, but didn't work well. How can I do this?
a = [
{id:1,name:'qw'group:'C',name:'hite',position:'1'},
{id:2,name:'qwe'group:'B',name:'ite',position:'2'},
{id:3,name:'qwer'group:'A',name:'ite',position:'3'},
{id:4,name:'qer'group:'D',name:'te',position:'4'},
{id:5,name:'wer'group:'C',name:'whit',position:'5'},
{id:6,name:'er'group:'B',name:'whi',position:'6'},
]
function groupDo(array){
var groups = [];
for (var i in array){
groups[array[i].group] = array[i].group;
}
for (var i in array){
if (groups[array[i].group] == array[i].group){
groups[array[i].group] = array[i];
}
}
}
Here's a simple straight forward answer:
var sortByPosition = function(obj1, obj2) {
return obj1.position - obj2.position;
};
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = {};
for (var i = 0; i < arr.length; i += 1) {
if(!grouped[arr[i].group]) {
grouped[arr[i].group] = [];
}
grouped[arr[i].group].push(arr[i]);
}
for (var group in grouped) {
grouped[group] = grouped[group].sort(sortByPosition);
}
console.log(grouped);
When you want to do stuff like this though, it's usually recommended to use a utility library like lodash or underscore.js, so that you don't have to "reinvent the wheel". Here's how it would look like using one of these libraries:
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = _.groupBy(arr, 'group');
for (var group in grouped) {
_.sortBy(grouped[group], 'position');
}
console.log(grouped);
Here ya go!
a = [
{id:1,name:'qw',group:'C',name:'hite',position:'1'},
{id:2,name:'qwe',group:'B',name:'ite',position:'2'},
{id:3,name:'qwer',group:'A',name:'ite',position:'3'},
{id:4,name:'qer',group:'D',name:'te',position:'4'},
{id:5,name:'wer',group:'C',name:'whit',position:'5'},
{id:6,name:'er',group:'B',name:'whi',position:'6'},
]
function groupAndSort(array, groupField, sortField) {
var groups = {}; // This object will end being keyed by groups, and elements will be arrays of the rows within the given array, which have been sorted by the sortField
// Put all the rows into groups
for (var i = 0; i < array.length; i++) {
var row = array[i];
var groupValue = row[groupField];
groups[groupValue] = groups[groupValue] || [];
groups[groupValue].push(row);
}
// Sort each group
for (var groupValue in groups) {
groups[groupValue] = groups[groupValue].sort(function(a, b) {
return a[sortField] - b[sortField];
});
}
// Return the results
return groups;
}
var groupedAndSorted = groupAndSort(a, "group", "position");
If you want to group objects, first think about what the resulting data would look like. Maybe something like this?
var grouped = {
A : [
{id:3,name:'qwer', group:'A',name:'ite',position:'3'}
],
B : [],
C : [],
D : []
};
And so on. To transform a list into an object, consider using .reduce().
.reduce() takes a function as its first argument, and a resulting object as the second. The function iterates through each element of the array and reduces it into the given object.
var data = [
{id:1,name:'qw', group:'C',name:'hite',position:'1'},
{id:2,name:'qwe', group:'B',name:'ite',position:'2'},
{id:3,name:'qwer', group:'A',name:'ite',position:'3'},
{id:4,name:'qer', group:'D',name:'te',position:'4'},
{id:5,name:'wer', group:'C',name:'whit',position:'5'},
{id:6,name:'er', group:'B',name:'whi',position:'6'},
]
// acc is the accumulated object, x is each element of the array
data.reduce(function(acc, x) {
// first check if the given group is in the object
acc[x.group] = acc[x.group] ? acc[x.group].concat(x) : [x];
return acc;
}, {}); // this is the resulting object
Now all you need to do is use the built in sort to order the resulting arrays. You could do this by iterating through the keys of the resulting object and applying .sort() to each array. .sort() takes a function as an argument which accesses the data and provides a comparison function.
// a and b are elements of the array
array.sort(function(a, b) {
if (a.position > b.position) {
return -1;
} else if (b.position > a.position) {
return 1;
} else {
return 0;
}
});
And you would implement it like so
var result = Object.keys(data).map(function(d){
return d.sort(f); // f is the function above
});

Categories