Best way of grouping array objects by multiple values in javascript.ES6 - javascript

Good day developers Im wondering how I could group an array of objects of different values in specific sub-groups , where in each sub-group my contain objects with specific values according to the key queried.
My array would be something like this
const cars =
[ { make: 'audi', model: 'r8', year: '2012' }
, { make: 'audi', model: 'rs5', year: '2013' }
, { make: 'ford', model: 'mustang', year: '2012' }
, { make: 'ford', model: 'fusion', year: '2015' }
, { make: 'kia', model: 'optima', year: '2012' }
]
And i would like to gather by the key make in a subgroup of name 2nd_class all objects that in the key make, had as value kia or ford, gathering the others in a group 1rst_class
Having as result an object like :
const expected =
[ '2nd_class':
[ { make: 'ford', model: 'mustang', year: '2012' }
, { make: 'ford', model: 'fusion', year: '2015' }
, { make: 'kia', model: 'optima', year: '2012' }
]
, '1rst_class' :
[ { make: 'audi', model: 'r8', year: '2012' }
, { make: 'audi', model: 'rs5', year: '2013' }
]
]
All the examples on the web always refer to grouping by key and one specific value ....
Any help would be amazing.

Or you can simply do this:
const cars = [
{ make: 'audi', model: 'r8', year: '2012' },
{ make: 'audi', model: 'rs5', year: '2013' },
{ make: 'ford', model: 'mustang', year: '2012' },
{ make: 'ford', model: 'fusion', year: '2015' },
{ make: 'kia', model: 'optima', year: '2012' }
];
const cars_in_classes=cars.reduce((a,c)=>{
const cls=(c.make==="audi"?"1st":"2nd")+"_class";
(a[cls]=a[cls]||[]).push(c);
return a;}, {} );
console.log(cars_in_classes);
The line (a[cls]=a[cls]||[]).push(c); checks whether the object property a[cls] already exists and - if not - creates it as an empty array before the current element is pushed onto it.
In case you consider several brands to be "1st_class" you could change line 2 to this:
const cls=(["audi","mercedes"].indexOf(c.make)>-1?"1st":"2nd")+"_class";

You need to do something like that:
const cars = [
{
'make': 'audi',
'model': 'r8',
'year': '2012'
}, {
'make': 'audi',
'model': 'rs5',
'year': '2013'
}, {
'make': 'ford',
'model': 'mustang',
'year': '2012'
}, {
'make': 'ford',
'model': 'fusion',
'year': '2015'
}, {
'make': 'kia',
'model': 'optima',
'year': '2012'
},
];
// Used car make
const usedMake = [];
// Return object
const formattedObject = {
'1st_class': [],
'2nd_class': []
};
// Iterate through your car array
cars.forEach(car => {
// Check if this is the first time we see this make and process
if (usedMake.indexOf(car.make) === -1) {
// Retrieve the cars with the same make as our iterated car
const filteredCars = cars.filter(c => c.make === car.make);
if (['kia', 'ford'].includes(car.make)) {
// push in our 2nd class - we push the retrieved objects
formattedObject['2nd_class'].push(...filteredCars)
} else {
// push in our 1st class - we push the retrieved objects
formattedObject['1st_class'].push(...filteredCars)
}
// Store the used car make so we don't reuse it later
usedMake.push(car.make);
}
});
console.log(formattedObject)
The kind of process iterate, check value unused, process if unused, store to prevent reuse is a basic programmation algorithm.

this way :
const cars =
[ { make: 'audi', model: 'r8', year: '2012' }
, { make: 'audi', model: 'rs5', year: '2013' }
, { make: 'ford', model: 'mustang', year: '2012' }
, { make: 'ford', model: 'fusion', year: '2015' }
, { make: 'kia', model: 'optima', year: '2012' }
]
const Class2 = [ 'ford', 'kia' ]
const expected = cars.reduce( (r,c) =>
{
let cls = Class2.includes(c.make) ? '2nd_class':'1rst_class'
r[cls].push({...c})
return r
} , {'2nd_class':[],'1rst_class':[] } )
console.log( expected )
.as-console-wrapper {max-height: 100%!important;top:0 }

Rather than just write a one-off solution for your particular case, I decided to try to create a generic grouping function. Because you're trying to group, not only by a single value (the usual way utilities group things), this type of grouping function requires a bit more input.
So, I created the function:
groupBy(arr, propToGroup, mapping)
It takes the array of objects to group, the property within those objects to examine for the grouping and a mapping object that tells you which values for that property belong in which group name.
Here's a version you can run here in the snippet:
function groupBy(arr, propToGroup, mapping, defaultMapping) {
let output = new Map();
for (let item of arr) {
// get value of our property of interest
let val = item[propToGroup];
if (val === undefined) {
if (defaultMapping) {
val = defaultMapping;
} else {
throw new Error(`No value for property .${propToGroup} and no defaultMapping`);
}
}
let classification = mapping.get(val);
if (!classification) {
if (!defaultMapping) {
throw new Error(`Property value ${val} is not present in mapping and no defaultMapping`);
}
classification = defaultMapping;
}
let classificationArray = output.get(classification);
// if classification not found yet, then initialize as empty array
if (!classificationArray) {
classificationArray = [];
output.set(classification, classificationArray);
}
classificationArray.push(item);
}
// convert to output format
let result = [];
for (let [key, val] of output) {
result.push({
[key]: val
});
}
return result;
}
const cars = [
{ make: 'audi', model: 'r8', year: '2012' },
{ make: 'audi', model: 'rs5', year: '2013' },
{ make: 'ford', model: 'mustang', year: '2012' },
{ make: 'ford', model: 'fusion', year: '2015' },
{ make: 'kia', model: 'optima', year: '2012' },
{ make: 'vw', model: 'bug', year: '1960' },
];
const mapping = new Map([
['audi', '1rst_class'],
['ford', '2nd_class'],
['kia', '2nd_class']
]);
let result = groupBy(cars, "make", mapping, "other");
console.log(result);
The idea is that you could reuse this groupBy() function in other circumstances too. If a given property value is not found in the mapping and a defaultMapping was passed, then it will be put in the defaultMapping bucket. If no defaultMapping was passed and it's not in the mapping, it will throw and exception.
Note, the defaultMapping adds a number of lines of code, but attempts to handle the cases of unexpected data or data where you want a "catchall" bucket that catches everything else that isn't specific in the mapping. That obviously isn't required for your specific question, but likely makes this more generally useful for other situations.
Function explanation:
Create a Map object for internal use to keep track of the groups encountered where the group name is the key and an array of objects in that group is the value for entry.
Iterate through the array of objects.
Get the property value of interest for the object.
If the property doesn't exist, attempt to use the default mapping
If the property does exist, look it up in the mapping to get its classification.
If no classification is found, attempt to use the defaultMapping
Lookup the classification in our temporary output Map
If not found, then create the empty array for this classification.
Add item to the classification array
When done iterating the array, convert the internal Map object to the desired final array structure and return it.

Related

Create sub array with key value and sort it

Given below is an array of objects, need to create sub-array in it with common make key, also all sub-array and main array needs to be sorted as follows:
the main array to be sorted on make key alphabetically.
sub-arrays to be sorted ASC on the basis of the year.
Input Array:
const cars = [
{
"make": "audi",
"model": "r8",
"year": "2006"
}, {
"make": "audi",
"model": "s5",
"year": "2005"
}, {
"make": "ford",
"model": "mustang",
"year": "2012"
}, {
"make": "ford",
"model": "fusion",
"year": "2015"
}, {
"make": "kia",
"model": "optima",
"year": "2012"
},
];
Desired Array:
const cars = [
{
"make": "audi",
"list": [
{
"model": "s5",
"year": "2005",
},
{
"model": "r8",
"year": "2006",
},
],
},
{
"make": "ford",
"list": [
{
"model": "mustang",
"year": "2012",
},
{
"model": "fusion",
"year": "2015",
},
],
},
{
"make": "kia",
"list": [
{
"model": "optima",
"year": "2012",
},
],
},
];
What I have tried yet:
let filteredData = [];
cars.forEach((value)=>{
var foundIndex = filteredData.findIndex( car => car.make === value.make );
if(foundIndex != -1){
let carData = filteredData[foundIndex];
const {list} = carData;
let nextObj = {'model': value.model, 'year': value.year};
const newData = [...list, nextObj];
carData['list'] = newData;
filteredData[foundIndex] = carData;
}else{
let values = {'model': value.model, 'year': value.year};
let data = [values];
let obj = {'make':value.make, 'list': data};
filteredData.push(obj);
}
});
const sortedCars = filteredData.sort((a, b) => a.make.localeCompare(b.make));
Any optimal solution for this to make it better is welcomed.
This is really just a 'group by' with sorting. To avoid having to sort multiple nested arrays in the result you can sort (a copy) of the array by year before grouping, and then sort the result by make afterwards. Here using a for...of loop grouping into an object and then sorting the Object.values() as the result.
const cars = [
{ make: 'ford', model: 'fusion', year: '2015' },
{ make: 'audi', model: 'r8', year: '2006' },
{ make: 'audi', model: 's5', year: '2005' },
{ make: 'ford', model: 'mustang', year: '2012' },
{ make: 'kia', model: 'optima', year: '2012' },
];
const grouped = {};
for (const { make, ...rest } of [...cars].sort((a, b) => a.year - b.year)) {
(grouped[make] ??= { make, list: [] }).list.push({ ...rest });
}
const result = Object.values(grouped).sort((a, b) =>
a.make.localeCompare(b.make)
);
console.log(result);
Alternatively, because javascript objects sort integer properties by default you can 'group by' year within each list and then map the resulting object values to arrays. The outer array will need to be sorted explicitly.
const cars = [
{ make: 'ford', model: 'fusion', year: '2015' },
{ make: 'audi', model: 'r8', year: '2006' },
{ make: 'audi', model: 's5', year: '2005' },
{ make: 'ford', model: 'mustang', year: '2012' },
{ make: 'kia', model: 'optima', year: '2012' },
];
const result = Object.values(
cars.reduce((a, { make, year, ...rest }) => {
a[make] ??= { make, list: {} };
a[make].list[year] ??= [];
a[make].list[year].push({ year, ...rest });
return a;
}, {}))
.map(({ make, list }) => ({ make, list: Object.values(list).flat() }))
.sort((a, b) => a.make.localeCompare(b.make));
console.log(result);
let filteredData = [];
cars.forEach((value)=>{
var foundIndex = filteredData.findIndex( car => car.make === value.make );
if(foundIndex != -1){
let carData = filteredData[foundIndex];
const {list} = carData;
let nextObj = {'model': value.model, 'year': value.year};
let newData = [...list, nextObj];
newData.sort((a, b) => a.year.localeCompare(b.year));
carData['list'] = newData;
filteredData[foundIndex] = carData;
}else{
let values = {'model': value.model, 'year': value.year};
let data = [values];
let obj = {'make':value.make, 'list': data};
filteredData.push(obj);
}
});
const sortedCars = filteredData.sort((a, b) => a.make.localeCompare(b.make));

Reconstruct nested object in Javascript

I have such object in Javascript and I need to reconstruct it like below :
let data = {
"10001": {
'brand': 'jeep',
'model': 'cherokee',
'price': 27335,
},
'10002': {
'brand': 'jeep',
'model': 'compas',
'price': 23775,
},
'32402': {
'brand': 'dodge',
'model': 'challenger',
'price': 29590,
}
};
like this :
let data_sorted = {
"jeep": {
10001: {
'brand': 'jeep',
'model': 'cherokee',
'price': 27335,
}, 10002: {
'brand': 'jeep',
'model': 'compas',
'price': 23775,
}
},
"dodge": {
32402: {
'brand': 'dodge',
'model': 'challenger',
'price': 29590,
}
}
};
Tried Object.assign but it merges duplicate keys .
Note brand name "jeep" is duplicate. I think it can be done using spread operator (...) three dots
You need some to buil new objects for the second level.
let data = { 10001: { brand: 'jeep', model: 'cherokee', price: 27335 }, 10002: { brand: 'jeep', model: 'compas', price: 23775 }, 32402: { brand: 'dodge', model: 'challenger', price: 29590 } },
result = Object
.entries(data)
.reduce(
(r, [k, v]) => ({ ...r, [v.brand]: { ...(r[v.brand] || {}), [k]: v } }),
{}
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Create the result object with the brands as keys, and with new objects as values. Then iterate the data to populate those new objects based on the brand:
let data = {"10001": {'brand': 'jeep','model': 'cherokee','price': 27335,},'10002': {'brand': 'jeep','model': 'compas','price': 23775,},'32402': {'brand': 'dodge','model': 'challenger','price': 29590,}};
let result = Object.fromEntries(Object.values(data).map(({brand}) => [brand, {}]));
Object.entries(data).forEach(([key, val]) => result[val.brand][key] = val);
console.log(result);

Filter an array of objects based on filter criteria in another array of objects: JavaScript

I wish to filter myArray based on criteria mentioned in myFilter.
The keys of myFilter are defined and could be accessed using myFilter.field, myFilter.value where as key:value of myArray are unknown.
We might have to iterate over each object in myArray to first match the myArray [key] with myFilter.field and then to that myArray [key] to myFilter.value.
That should be an AND logic
myArray = [{
make: "Honda",
model: "CRV",
year: "2017"
},
{
make: "Toyota",
model: "Camry",
year: "2020"
},
{
make: "Chevy",
model: "Camaro",
year: "2020"
}
]
myFilter = [{
field: "make",
value: "Chevy",
type: "string"
},
{
field: "year",
value: "2020",
type: "date"
}
];
// Expected OutPut:
myArray = [{
make: "Chevy",
model: "Camaro",
year: "2020"
}]
var tempArray = [];
const keysToMatch = myFilter.length;
let matchedItems = [];
myArray.forEach((data) => {
matchedItems = [];
let itemsToFind = Object.values(data);
myFilter.forEach((filterItem) => {
if (itemsToFind.indexOf(filterItem.value) != -1) {
matchedItems.push("matched");
}
});
//check if everything matched
if (matchedItems.length === keysToMatch) {
tempArray.push(data);
}
});
console.log(tempArray);
var tempArray = [];
const keysToMatch = myFilter.length;
let matchedItems = [];
myArray.forEach((data) => {
matchedItems = [];
let itemsToFind = Object.values(data);
myFilter.forEach((filterItem) => {
if (itemsToFind.indexOf(filterItem.value) != -1) {
matchedItems.push("matched");
}
});
//check if everything matched
if (matchedItems.length === keysToMatch) {
tempArray.push(data);
}
});
console.log(tempArray);
You can use normal for loop and array filter. Define a variable filteredArray and use for loop to iterate myFilter. During each iteration create a variable k whose value will be set to the filtered array. So at the first step the initial value of k will be the myArray and k will be filtered and the filtered value will be set to filteredArray. During second iteration and so on the value of k will be set to the first filtered array
let myArray = [{
make: "Honda",
model: "CRV",
year: "2017"
},
{
make: "Toyota",
model: "Camry",
year: "2020"
},
{
make: "Chevy",
model: "Camaro",
year: "2020"
}
]
let myFilter = [{
field: "make",
value: "Chevy",
type: "string"
},
{
field: "year",
value: "2020",
type: "date"
}
];
let filteredArray;
for (let i = 0; i < myFilter.length; i++) {
let k = filteredArray !== undefined ? filteredArray : myArray
if (myFilter[i].field === 'make') {
filteredArray = k.filter(item => item[myFilter[i].field] === myFilter[i].value)
} else if (myFilter[i].field === 'year') {
filteredArray = k.filter(item => item[myFilter[i].field] === myFilter[i].value)
}
}
console.log(filteredArray)
This should theoretically work, but for some reason doesn't (returns an empty array). I'm hoping for some other reader's heads up to make it work! Feel free to edit.
myArray = [{
make: "Honda",
model: "CRV",
year: "2017"
},
{
make: "Toyota",
model: "Camry",
year: "2020"
},
{
make: "Chevy",
model: "Camaro",
year: "2020"
}
]
myFilter = [{
field: "make",
value: "Chevy",
type: "string"
},
{
field: "year",
value: "2020",
type: "date"
}
];
const myNewArray = myArray.filter((car) => myFilter.every((filter) => car[filter.field] === car[filter.value]));
console.log(myNewArray);
This might be a bit over complicated for what you need, but it works.
myArray = [
{
make: "Honda",
model: "CRV",
year: "2017"
},
{
make: "Toyota",
model: "Camry",
year: "2020"},
{
make: "Chevy",
model: "Camaro",
year: "2020"}
]
myFilter = [
{
field: "make",
value: "Chevy",
type: "string"
},
{
field: "year",
value: "2020",
type: "date"
}
];
//only return those that return true
var newArray = myArray.filter(car => {
var temp = true;
//iterate over your filters
for (var i = 0; i < myFilter.length; i++) {
//if any filters result in false, then temp will be false
if (car[myFilter[i].field] != myFilter[i].value) {
temp = false;
}
}
if (temp == true) {
return true;
} else {
return false;
}
});
console.log(JSON.stringify(newArray));

Add a property with .map() in rxjs

If I had an object car:
{
make: "Chevy",
model: "Sonic"
type: {
id: 1,
name: "Tiny"
}
}
And I wanted to manipulate the object to add a typeId property:
{
make: "Chevy",
model: "Sonic"
type: {
id: 1,
name: "Tiny"
},
typeId: 1
}
Which rxjs operator would you use, assuming you had an array of these objects? This is what I'm trying to avoid:
someArray.map(item => ({
make: item.make,
model: item.model,
type: item.type,
typeId: item.type.id
}));
To add a property in each object in the array, you could use forEach as you don't have to map the array
someArray.forEach(o => o.typeId = o.type.id)

Can I customize select to show the selected value different than what I've selected

I need to show hierarchy in the usual select control. I implemented this by using \u00A0 inside of the option labels.
The hierarchy looks like this:
Audi
- A1
- A2
- A3
- A4
Ford
- Galaxy
- Mondeo
- Focus
But when the value selected I need to show the whole value (for ex. "Audi - A4"). And if I expand the select again I still need to see the initial options list with "- A4" selected. Js/angular solution is fine here.
Please, advice.
The best way to get this hierarchy is to use the grouping in ng-options directive, see plunker.
$scope.values = [
{ id: 1, make: 'Audi', type: 'A1' },
{ id: 2, make: 'Audi', type: 'A2' },
{ id: 3, make: 'Audi',type: 'A3' },
{ id: 4, make: 'Audi', type: 'A4' },
{ id: 5, make: 'Ford', type: 'Galaxy' },
{ id: 6, make: 'Ford', type: 'Mondeo' },
{ id: 7, make: 'Ford', type: 'Focus' }
];
$scope.selectedId = 1;
and then
<select ng-options="item.id as item.make + ' ' + item.type group by item.make for item in values" ng-model="selectedId"></select>
This has the full name of the car in the select, groups by the make, but still, the individual options have the full name of the car.

Categories

Resources