Create sub array with key value and sort it - javascript

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));

Related

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

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.

Splitting a Map instance into two separate arrays

I'm trying to split an instance of Map into separate arrays. An example of the Map instance I have:
new Map([
["Guatemala", 7],
["Albania", 7],
["Finland", 3],
["Canada", 12],
["Japan", 21],
...
]);
There's more data, but just wanted to show a small sample.
I produced it from the raw data I get from an API in JSON format:
[
{
id: 1,
import_country: "Argentina",
model: "riolet",
make: "Audi",
sold_by: "Huey Bagster",
sale_price: 18643,
},
{
id: 2,
import_country: "China",
model: "MKX",
make: "Lincoln",
sold_by: "Wolf Coller",
sale_price: 16850,
},
{
id: 3,
import_country: "Portugal",
model: "Coupe Quattro",
make: "Audi",
sold_by: "Doroteya McLewd",
sale_price: 13733,
},
]
So I took the import_country and counted the number of times it appeared and created map as an instance of Map.
In order to get the keys and values of map in separate arrays, I tried the following:
map.forEach(key, value){
country.append(key);
numCountry.append(value);
}
where country and numCountry are separate arrays.
My end desired output is:
country = ["Guatemala", "Albania"...] and
numCountry = [7, 7, ...]
If your map is an instance of Map, then use its keys and values methods:
const map = new Map([
["Guatemala", 7],
["Albania", 7],
["Finland", 3],
["Canada", 12],
["Japan", 21],
]);
const countries = [...map.keys()];
const numCountries = [...map.values()];
console.log(countries);
console.log(numCountries);
Kindly take as sample, modify according to your need.
You can use Object.reduce(data) to loop over data and update count for countries.
const data = [
{
id: 1,
import_country: "Argentina",
model: "riolet",
make: "Audi",
sold_by: "Huey Bagster",
sale_price: 18643,
},
{
id: 2,
import_country: "China",
model: "MKX",
make: "Lincoln",
sold_by: "Wolf Coller",
sale_price: 16850,
},
{
id: 3,
import_country: "Portugal",
model: "Coupe Quattro",
make: "Audi",
sold_by: "Doroteya McLewd",
sale_price: 13733,
},
{
id: 4,
import_country: "Portugal",
model: "Coupe Quattro",
make: "Audi",
sold_by: "Doroteya McLewd",
sale_price: 13733,
},
];
const map = data.reduce((m, { id, import_country }) => {
if (!m[import_country]) m[import_country] = 0;
m[import_country] += 1;
return m;
}, {});
console.log(map);
const counties = Object.keys(map)
const numCounts = Object.values(map)
console.log(counties)
console.log(numCounts)
//OR:
let countries2 = [],
numCountries2 = [];
Object.entries(map).forEach(([key, value]) => {
countries2.push(key)
numCountries2.push(value)
});
console.log(countries2);
console.log(numCountries2);
Use JSON.parse to convert it to an array then just loop and push id of each item on country code array and name on ct array
var str = `[
{
"id": 1,
"import_country": "Argentina",
"model": "riolet",
"make": "Audi",
"sold_by": "Huey Bagster",
"sale_price": 18643
},
{
"id": 2,
"import_country": "China",
"model": "MKX",
"make": "Lincoln",
"sold_by": "Wolf Coller",
"sale_price": 16850
},
{
"id": 3,
"import_country": "Portugal",
"model": "Coupe Quattro",
"make": "Audi",
"sold_by": "Doroteya McLewd",
"sale_price": 13733
}
]`
var arr = JSON.parse(str);
var ct = [];
var code = [];
for (var i = 0; i < arr.length; i++) {
ct.push(arr[i].import_country);
code.push(arr[i].id)
}
console.log(ct); //countries
console.log(code); //codes

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));

GroupBy items Using Underscore based on two keys

I've one object, which I want to be group by based on two keys.
var obj = [{
make: "nissan",
model: "sunny",
colour: "red"
},
{
make: "nissan",
model: "sunny",
colour: "red"
},
{
make: "nissan",
model: "sunny",
colour: "red1"
}];
var result = _.groupBy(obj, p=>p.model);
gives me one result.
I want this to be group Base on model and color, so that I've two results as:
result = [{
make: "nissan",
model: "sunny",
colour: "red"
},
{
make: "nissan",
model: "sunny",
colour: "red"
}];
How I can do this with the help of Underscore js or any other short way.
With underscore.js groupBy you can group multiple properties like this:
const obj = [{make: "nissan",model: "sunny",colour: "red"}, {make: "nissan",model: "sunny",colour: "red"},{make: "nissan",model: "sunny",colour: "red1"}];
const result = _.groupBy(obj, item => item.model + '#' + item.colour);
console.log(result);
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
But the result you show in your question looks like you need is Array.prototype.filter():
const obj = [{make: "nissan",model: "sunny",colour: "red"}, {make: "nissan",model: "sunny",colour: "red"},{make: "nissan",model: "sunny",colour: "red1"}];
const result = obj.filter(item => item.model === 'sunny' && item.colour === 'red');
console.log(result);
since you asked "How I can do this with the help of Underscore js or any other short way" , here is a short way using Array.prototype.filter()
var obj = [{
make: "nissan",
model: "sunny",
colour: "red"
},{
make: "nissan",
model: "sunny",
colour: "red"
},
{
make: "nissan",
model: "sunny",
colour: "red1"
}];
var res = obj.filter( key => key.colour === "red")
console.log(res)
var result = _.groupBy(obj, function(o){
return o.model + o.color;
});

Filter through array of objects in javascript

I have a data structure that looks like this:
const carsData = [
{
name: "Cars",
collection: [
{ year: 2011, model: "B", price: 4400 },
{ year: 2015, model: "A", price: 32000 },
{ year: 2016, model: "B", price: 15500 }
]
},
{
name: "Trucks",
collection: [
{ year: 2014, model: "D", price: 18000 },
{ year: 2013, model: "E", price: 5200 }
]
},
{
name: "Convertibles",
collection: [
{ year: 2009, model: "F", price: 20000 },
{ year: 2010, model: "G", price: 8000 },
{ year: 2012, model: "H", price: 12500 },
{ year: 2017, model: "M", price: 80000 }
]
}
];
and want to return a new array let's say const newCarsData(see below) where collection consists of only objects with year higher than 2013, so it will look like this:
const newCarsData = [
{
name: "Cars",
collection:[
{ year: 2015, model: "A", price: 32000 },
{ year: 2016, model: "B", price: 15500 }
]
},
{
name: "Trucks",
collection: [
{ year: 2014, model: "D", price: 18000 }
]
},
{
name: "Convertibles",
collection: [
{ year: 2017, model: "M", price: 80000 }
]
}
];
I tried filter method collection.filter(x => x.year > 2013) inside of for loop, but couldn't make it work. At the end my code looked like this
const newCarsData = getNewData(carsData);
let arr = [];
function getNewData(somedata) {
for (let i = 0; i < somedata.length; i++) {
// console.log(somedata[i].collection);
for (let j = 0; j < somedata[i].collection.length; j++) {
let arr.push(somedata[i].collection[j]);
// console.log(somedata[i].collection[j]);
}
// return somedata[i].collection.filter(x => x.year > 2013);
}
return arr.filter(x => x.year > 2013);
}
Since the collection is in another array inside the array items, you can't directly use filter. You can use map first then use filter.
const carsData=[{name:"Cars",collection:[{year:2011,model:"B",price:4400},{year:2015,model:"A",price:32000},{year:2016,model:"B",price:15500}]},{name:"Trucks",collection:[{year:2014,model:"D",price:18000},{year:2013,model:"E",price:5200}]},{name:"Convertibles",collection:[{year:2009,model:"F",price:20000},{year:2010,model:"G",price:8000},{year:2012,model:"H",price:12500},{year:2017,model:"M",price:80000}]}]
const filteredCarData = carsData.map(carType => {
return {
...carType,
collection: carType.collection.filter(car => car.year>2013)
}
})
console.log(JSON.stringify(filteredCarData))
The ...carType notation collects the object properties in the new mapped object. If you have no other properties than name, you can instead do
const filteredCarData = carsData.map(carType => {
return {
name: carType.name,
collection: carType.collection.filter(car => car.year>2013)
}
})
You'll have to update your collection:
carsData.forEach(function(carData){
carData.collection = carData.collection.filter(x => x.year > 2013);
});
One way to do it can be using reduce.
var res = carsData.reduce((acc, value) => {
let data = { name: value.name, collections: value.collection.filter(v => v.year > 2013 )}
return acc.concat(data)
}, [])
You can actually, replace the reduce with a map:
carsData.map(value => {
return { name: value.name, collections: value.collection.filter(v => v.year > 2013 )}
})

Categories

Resources