Javascript - group array of objects by common values with label - javascript

I am trying to turn an array of objects into another array of objects by grouping by a specific value and adding that value as label and taking it out of the object in the new array.
Input: So for instance I have this array of objects:
let tech = [
{ id: 1, grouping: "Front End", value: "HTML" },
{ id: 2, grouping: "Front End", value: "React" },
{ id: 3, grouping: "Back End", value: "Node" },
{ id: 4, grouping: "Back End", value: "PHP" },
];
Expected: I am looking to try and figure out how I can get to this, where there is a label for each of the unique groupings and options array containing the values of that grouping.
[
{
label: "Front End",
options: [
{ id: 1, value: "HTML" },
{ id: 2, value: "React" },
],
},
{
label: "Back End",
options: [
{ id: 3, value: "Node" },
{ id: 4, value: "PHP" },
],
},
]
The closest I have been able to get to is using reduce to group by the grouping key:
const groupedTech = tech.reduce((acc, value) => {
// Group initialization
if (!acc[value.grouping]) {
acc[value.grouping] = [];
}
// Grouping
acc[value.grouping].push(value);
return acc;
}, {});
Which gives me this:
{
"Front End": [
{ id: 1, grouping: "Front End", value: "HTML" },
{ id: 2, grouping: "Front End", value: "React" },
],
"Back End": [
{ id: 3, grouping: "Back End", value: "Node" },
{ id: 4, grouping: "Back End", value: "PHP" },
],
}
But this returns object not an array and doesn't remove the grouping value. I have not been able to figure out how to group properly because in the array of objects I have not found an efficient way to compare against to see if the grouping exists and if so add to that nested array. Would I be better off using something like .map()? Appreciate any leads/learnings!

You're very close, just wrap the key-value entries of the result you've got in a map function:
let tech = [
{ id: 1, grouping: "Front End", value: "HTML" },
{ id: 2, grouping: "Front End", value: "React" },
{ id: 3, grouping: "Back End", value: "Node" },
{ id: 4, grouping: "Back End", value: "PHP" },
];
const groupedTech = Object.entries(
// What you have done
tech.reduce((acc, { id, grouping, value }) => {
// Group initialization
if (!acc[grouping]) {
acc[grouping] = [];
}
// Grouping
// FIX: only pushing the object that contains id and value
acc[grouping].push({ id, value });
return acc;
}, {})
).map(([label, options]) => ({ label, options }));
console.log(groupedTech);

You just have to do one more manipulation with Object.entries and .map
let tech = [
{ id: 1, grouping: 'Front End', value: 'HTML' },
{ id: 2, grouping: 'Front End', value: 'React' },
{ id: 3, grouping: 'Back End', value: 'Node' },
{ id: 4, grouping: 'Back End', value: 'PHP' }
]
const groupedTech = tech.reduce((acc, value) => {
// Group initialization
if (!acc[value.grouping]) {
acc[value.grouping] = []
}
// Grouping
acc[value.grouping].push(value)
return acc
}, {})
const res = Object.entries(groupedTech).map(([label, options]) => ({
label,
options
}))
console.log(res)

A minor variation on the other two answers if you want to get exactly the output you specify:
let tech = [{
id: 1,
grouping: "Front End",
value: "HTML"
},
{
id: 2,
grouping: "Front End",
value: "React"
},
{
id: 3,
grouping: "Back End",
value: "Node"
},
{
id: 4,
grouping: "Back End",
value: "PHP"
},
];
const groupedTech = Object.entries(
tech.reduce((acc, value) => {
// Group initialization
if (!acc[value.grouping]) {
acc[value.grouping] = [];
}
// Grouping
acc[value.grouping].push({
id: acc[value.grouping].length+1,
value: value.value
});
return acc;
}, {}))
.map(([label, options]) => ({
label,
options
}));
console.log(groupedTech);

I usually like to build up a Map of key / value pairs then transform those entries into the final result (usually using Array.prototype.map() or Array.from()).
const tech = [
{ id: 1, grouping: "Front End", value: "HTML" },
{ id: 2, grouping: "Front End", value: "React" },
{ id: 3, grouping: "Back End", value: "Node" },
{ id: 4, grouping: "Back End", value: "PHP" },
];
const groupedMap = tech.reduce((map, { grouping, ...option }) => {
if (!map.has(grouping)) {
map.set(grouping, [])
}
map.get(grouping).push(option)
return map
}, new Map())
const groupedTech = Array.from(groupedMap, ([ label, options ]) => ({
label,
options
}))
console.log(groupedTech)

Using a Map and Map#values()
const grouped = tech.reduce((m,{grouping:label, ...rest})=>{
const group = m.get(label) || {label, options:[]};
group.options.push({...rest})
return m.set(label, group)
},new Map)
console.log([...grouped.values()])
<script>
let tech=[{id:1,grouping:"Front End",value:"HTML"},{id:2,grouping:"Front End",value:"React"},{id:3,grouping:"Back End",value:"Node"},{id:4,grouping:"Back End",value:"PHP"}];
</script>

Related

Filter 2 arrays to check if parent child

I have first array -
let parent = [
{
id:1,
value:"ABC",
},
{
id:2,
value:"DEF",
},
{
id:3,
value:"GHI",
},
{
id:4,
value:"JKL",
},
{
id:5,
value:"MNO",
},
{
id:6,
value:"PQR",
},
]
And 2nd Array Object -
let child = [
{
childid:1,
value:"ABC",
},
{
childid:2,
value:"DEF",
},
{
childid:10,
value:"GHI",
},
]
From parent array I want to select all those elements whose id matches with childid from child array.
I tried -
parent.filter(x=>x.id==child.each(y=>y.childid))
But its not working
You can use some() to do it
let parent = [
{
id:1,
value:"ABC",
},
{
id:2,
value:"DEF",
},
{
id:3,
value:"GHI",
},
{
id:4,
value:"JKL",
},
{
id:5,
value:"MNO",
},
{
id:6,
value:"PQR",
},
]
let child = [
{
childid:1,
value:"ABC",
},
{
childid:2,
value:"DEF",
},
{
childid:10,
value:"GHI",
},
]
let result = parent.filter(p => child.some(a => a.childid == p.id ))
console.log(result)
using Flatmap and filter ...
let parent = [{
id: 1,
value: "ABC",
},
{
id: 2,
value: "DEF",
},
{
id: 3,
value: "GHI",
},
{
id: 4,
value: "JKL",
},
{
id: 5,
value: "MNO",
},
{
id: 6,
value: "PQR",
},
]
let child = [{
childid: 1,
value: "ABC",
},
{
childid: 2,
value: "DEF",
},
{
childid: 10,
value: "GHI",
},
]
const res = parent.flatMap(x => child.filter(y => y.childid === x.id))
console.log(res)
This would work
parent.filter(p => child.some(c => c.childid === p.id))
Wat happens is
For each element in parent array, find the corresponding element in the child array
If it exists the filter will see it as truthy and keep the parent element, if not it will be falsy and filter wil discard it
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
const filterResult = parent.filter(x => child.some(y => y.childid == x.id))
You can use a reduce function along with a forEach to loop through the child elements and compare against the parent.
const result = parents.reduce((acc, parent) => {
children.forEach((child) => {
if (parent.id === child.childid) {
acc.push(parent);
}
});
return acc;
}, []);
console.log(result); // [{"id":1,"value":"ABC"},{"id":2,"value":"DEF"}]
const parents = [{
id: 1,
value: 'ABC',
},
{
id: 2,
value: 'DEF',
},
{
id: 3,
value: 'GHI',
},
{
id: 4,
value: 'JKL',
},
{
id: 5,
value: 'MNO',
},
{
id: 6,
value: 'PQR',
},
];
const children = [{
childid: 1,
value: 'ABC',
},
{
childid: 2,
value: 'DEF',
},
{
childid: 10,
value: 'GHI',
},
];
const result = parents.reduce((acc, parent) => {
children.forEach((child) => {
if (parent.id === child.childid) {
acc.push(parent);
}
return acc;
});
return acc;
}, []);
console.log(result);
MDN Reduce

How to can I do this array desctructure with condition?

I am facing one problem with array destructuring, Please read my questions-
This is array 1-
const Array1 = [
{
label: "Fashion",
value: 1
},
{
label: "Electronics",
value: 2
}
]
This is array2-
const Array2 = [
{
id: 1,
values: [
{ value: "S", meta: "s" },
{ value: "M", meta: "m" },
{ value: "Xl", meta: "xl" },
]
},
{
id: 2,
values: [
{ value: "Red", meta: "red" },
{ value: "Yellow", meta: "yellow" },
{ value: "Green", meta: "green" },
]
}
]
I have to combine this two array when Id(array2) matched to value(array1) and also change field label- like I need actually like this-
const Array3 = [
{
name: "Fashion",
options: [
{ value: "S", label: "s" },
{ value: "M", label: "m" },
{ value: "Xl", label: "xl" },
]
},
{
name: "Electronics",
options: [
{ value: "Red", label: "red" },
{ value: "Yellow", label: "yellow" },
{ value: "Green", label: "green" },
]
}
]
I have already tried in this way-
const Array3 = Array1.map((item) => {
return {
name: item.label,
values: [],
options: Array2.map((e: any) => {
if (e.id === item.value) {
return e.values.map((v: any) => {
return {
label: v.meta,
value: v.value
}
})
}
})
}
})
From this function I am getting - one extra field with undefined-
Please click to see
But it's not working. Please help me by giving a correction of my functions.
You could take an object for the names and map new objects with name instead of id as properties.
const
array1 = [{ label: "Fashion", value: 1 }, { label: "Electronics", value: 2 }],
array2 = [{ id: 1, values: [ { value: "S", meta: "s" }, { value: "M", meta: "m" }, { value: "Xl", meta: "xl" }] }, { id: 2, values: [{ value: "Red", meta: "red" }, { value: "Yellow", meta: "yellow" }, { value: "Green", meta: "green" }] }],
names = Object.fromEntries(array1.map(({ label, value }) => [value, label])),
result = array2.map(({ id, values }) => ({
name: names[id],
options: values.map(({ meta: label, ...o }) => ({ ...o, label }))
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The reason why Array2.map doesn't work is that map function always returns the same number of elements as the array you run .map on. So adding a condition results in undefined elements when condition doesn't match. You can do this:
options: Array2.find(e => e.id === item.value).values.map(v => {
return { value: v.value, label: v.meta }
})
While this works, I'd recommend taking a look at #Nina Scholz's answer too as it makes use of Object/Dictionary which is much more efficient than running .find on Array2. O(1) vs O(n). So, if you expect to have lots of elements in Array2 or run this quite frequently then the more efficient solution would help
Maybe this is what you want?
const Array3 = Array1.map(array1Item => {
const foundIndex = Array2.findIndex(array2Item => array2Item.id === array1Item.value);
if(foundIndex !== -1) {
return {
name: array1Item.label,
options: Array2[foundIndex].values
}
};
return undefined;
});
console.log(Array3);
/**
[{
name: "Fashion"
options: Array(3)
0: {value: 'S', meta: 's'}
1: {value: 'M', meta: 'm'}
2: {value: 'Xl', meta: 'xl'}
},{
name: "Electronics"
options: Array(3)
0: {value: 'Red', meta: 'red'}
1: {value: 'Yellow', meta: 'yellow'}
2: {value: 'Green', meta: 'green'}
}]
*/

loop through array of objects and get all object values as a single string

I have an array of object
const arr = [
{ id: 1, value: "Apple" },
{ id: 1, value: "Orange" },
{ id: 1, value: "Pine Apple" },
{ id: 1, value: "Banana" },
];
I want to loop through this array and get all the fruit names as a single string every fruit will separated with comma. But don't know how to do that. Does anybody help me to do this?
You can use map and join like this
const arr = [
{ id: 1, value: "Apple" },
{ id: 1, value: "Orange" },
{ id: 1, value: "Pine Apple" },
{ id: 1, value: "Banana" },
];
const result = arr.map(({ value }) => value).join(', ')
console.log(result)
const fruitNames = arr.map(fruit => fruit.value).toString();

extract id from array using map with condition javascript

There is an array of objects
const groups = [
{ id: 0, name: "All", selected: false },
{ id: -1, name: "All", selected: true },
{ id: 1, name: "Group1", selected: false },
{ id: 2, name: "Group2", selected: false },
{ id: 3, name: "Group3", selected: false },
{ id: 4, name: "Group4", selected: true }
];
I want to extract ids from this object with map
groups.map(group => group.id > 0 && group.selected ? group.id:null)
but the result will be
[null,null,4,null...]
actually it should be [4]
I know I can use another function like forEach and push or map and filter but I would solve it with one iteration with map or something else.
Filter the object/s under your criteria and then extract the id/s with a map
const groups = [{
id: 0,
name: "All",
selected: false
},
{
id: -1,
name: "All",
selected: true
},
{
id: 1,
name: "Group1",
selected: false
},
{
id: 2,
name: "Group2",
selected: false
},
{
id: 3,
name: "Group3",
selected: false
},
{
id: 4,
name: "Group4",
selected: true
}
];
const result = groups.filter(x => x.id > 0 && x.selected).map(x => x.id)
console.log(result)
you can use a transducer in this case, so that you will not iterate through the array 2 times.
const groups = [
{ id: 0, name: "All", selected: false },
{ id: -1, name: "All", selected: true },
{ id: 1, name: "Group1", selected: false },
{ id: 2, name: "Group2", selected: false },
{ id: 3, name: "Group3", selected: false },
{ id: 4, name: "Group4", selected: true }
];
const filteredIds = groups.reduce(
(ids, { id, selected }) => (
id > 0 && selected ? [...ids, id] : ids
), []
);
console.log(filteredIds);
The map() method creates a new array with the results of calling a function for every array element and extraction is not possible with this. Either use map() and then discard the array items or use filter().
Better approach would be using filter(). The filter() method creates an array filled with all array elements that pass a test (provided as a function).
let result = groups.filter(x => x.id > 0 && x.selected).map(x => x.id)
You can easily do this in one iteration with transducers.
const getPositiveSelectedIDs = pipe([
filter(and([
gt(get('id'), 0),
get('selected'),
])),
map(get('id')),
])
transform(getPositiveSelectedIDs, [])(groups) // => [4]
In this example, getPositiveSelectedIDs is a transducer we declared using functions pipe, map, and filter. The predicate passed to filter uses functions and, gt, and get to say
only let groups through who have positive ids and who have been selected
then, without creating any intermediate arrays, we get the ids of each group with map and get. getPositiveSelectedIDs behaves as a transducer when we use it in transform. The flow of data starts when we call the transformation transform(getPositiveSelectedIDs, []) with groups.
More on transducers

Find Ids from multi dimensional object array

I have a multi dimensional object array:
var products = [
{ id: 1, groups: [ { id: 1.1, selected: true }, { id: 1.2, selected: false }},
{ id: 2, groups: [ { id: 2.1, selected: false }, { id: 2.2, selected: true }} ];
How can I find list of selected groups (group with selected flag set to true) in a single dimensional array using ES6.
Expected Result:
[1.1, 2.2]
You could use reduce method and inside one more forEach loop to get group objects where selected is true.
var products = [
{ id: 1, groups: [ { id: 1.1, selected: true }, { id: 1.2, selected: false }]},
{ id: 2, groups: [ { id: 2.1, selected: false }, { id: 2.2, selected: true }]} ];
const result = products.reduce((r, {groups}) => {
groups.forEach(e => e.selected && r.push(e.id));
return r;
}, [])
console.log(result)
A possible alternative to all the other answers.
Flatten the array, then extract the required info.
const products = [
{ id: 1, groups: [{ id: 1.1, selected: true }, { id: 1.2, selected: false }] },
{ id: 2, groups: [{ id: 2.1, selected: false }, { id: 2.2, selected: true }] }];
const allIds = [].concat(...products.map(p => p.groups)).filter(g => g.selected).map(x => x.id)
console.log(allIds)
You can use reduce and filter like below, this will give you all ids that have selected: true. Note that I changed id: 1.2 to be true to demonstrate the ability of getting multiple true results.
const products = [
{ id: 1, groups: [ { id: 1.1, selected: true }, { id: 1.2, selected: true } ]},
{ id: 2, groups: [ { id: 2.1, selected: false }, { id: 2.2, selected: true } ]} ];
const res = products.reduce((a, {groups}) =>
a.concat(...groups.filter(({selected}) => selected).map(({id}) => id))
, []);
console.log(res);
var products = [
{ id: 1, groups: [ { id: 1.1, selected: true }, { id: 1.2, selected: false }]},
{ id: 2, groups: [ { id: 2.1, selected: false }, { id: 2.2, selected: true } ]}];
var desiredResult = products.map(product => {
return product.groups.filter(group => group.selected).map(item => item.id).toString();
});
console.log(desiredResult);
you can make use of map,filter and concat function and get result
var products = [
{ id: 1, groups: [ { id: 1.1, selected: true }, { id: 1.3, selected: true },{ id: 1.2, selected: false }]},
{ id: 2, groups: [ { id: 2.1, selected: false }, { id: 2.2, selected: true }]} ];
const selectgroups = [].concat(...products.map(x => x.groups.filter(g=> g.selected)));
const selectedGroupIds = selectgroups.map(g=> g.id);
console.log(selectedGroupIds);
code which provide list of all selected groups
Working Demo
I see a completely different approach: using specialized data structures to model the data the way you want to work with it:
// Store product id mapped to a set of
// group ids
const productGroupMap = new Map ( [
[ 1, new Set ( [ 1.1, 1.2 ] ) ],
[ 2, new Set ( [ 2.1, 2.2 ] ) ]
] )
// Store group ids to their data
const groupMap = new Map ( [
[ 1.1, { title: "Title 1.1" } ],
[ 1.2, { title: "Title 1.2" } ],
[ 2.1, { title: "Title 2.1" } ],
[ 2.2, { title: "Title 2.2" } ]
] )
// Somewhere in your application, you would declare
// a set of selected group ids, and you would add ids
// when the user selects a given group
const selectedGroups = new Set ()
selectedGroups.add ( 1.1 )
selectedGroups.add ( 2.2 )
// Finally, when you want these selected groups, you just need to
// map selected group ids to their group data getting it from the
// groupMap
const groups = [ ...selectedGroups ].map ( id => groupMap.get ( id ) )
console.log ( groups )

Categories

Resources