Reputation: 179
I have an array of objects like below,
cars = [
{id: 1, make: 'audi', year: '2010', someProperty: true},
{id: 2, make: 'bmw', year: '2011', someProperty: false},
{id: 3, make: 'bmw', year: '2011', someProperty: true},
{id: 4, make: 'vw', year: '2010', someProperty: true},
{id: 5, make: 'vw', year: '2011', someProperty: true},
{id: 6, make: 'audi', year: '2011', someProperty: true},
{id: 7, make: 'bmw', year: '2010', someProperty: false},
{id: 8, make: 'bmw', year: '2011', someProperty: false},
{id: 9, make: 'bmw', year: '2010', someProperty: true}
]
I want to format it as below,
requiredFormat = [{
somePropertyTrue: [
{id: 1, make: 'audi', year: '2010', someProperty: true},
{id: 4, make: 'vw', year: '2010', someProperty: true},
{id: 9, make: 'bmw', year: '2010', someProperty: true}
],
somePropertyFalse: [
{id: 7, make: 'bmw', year: '2010', someProperty: false}
],
year: '2010'
}, {
somePropertyTrue: [
{id: 3, make: 'bmw', year: '2011', someProperty: true},
{id: 5, make: 'vw', year: '2011', someProperty: true},
{id: 6, make: 'audi', year: '2011', someProperty: true}
],
somePropertyFalse: [
{id: 2, make: 'bmw', year: '2011', someProperty: false},
{id: 8, make: 'bmw', year: '2011', someProperty: false}
],
year: '2011'
}]
Basically it is to group by year and someProperty = true/false, but want it to be formatted this way. I cannot use any plugin to do this, can use ES6.
I tried using reduce by writing a function that groups by a single property, but I am not sure how do we do it for multiple properties and project it this way. Please find my code to group and what I was able to achieve below,
let groupBy = function(arr, key) {
return arr.reduce(function(r, x) {
(r[x[key]] = r[x[key]] || []).push(x);
return r;
}, {});
};
I was able to achieve below,
ableToDo = [{
'2010' : [
{id: 1, make: 'audi', year: '2010', someProperty: true},
{id: 4, make: 'vw', year: '2010', someProperty: true},
{id: 7, make: 'bmw', year: '2010', someProperty: false},
{id: 9, make: 'bmw', year: '2010', someProperty: true}
],
'2011' : [
{id: 2, make: 'bmw', year: '2011', someProperty: false},
{id: 3, make: 'bmw', year: '2011', someProperty: true},
{id: 5, make: 'vw', year: '2011', someProperty: true},
{id: 6, make: 'audi', year: '2011', someProperty: true},
{id: 8, make: 'bmw', year: '2011', someProperty: false}
]}]
Upvotes: 2
Views: 338
Reputation: 1146
Please use the following code:
var cars = [
{id: 1, make: 'audi', year: '2010', someProperty: true},
{id: 2, make: 'bmw', year: '2011', someProperty: false},
{id: 3, make: 'bmw', year: '2011', someProperty: true},
{id: 4, make: 'vw', year: '2010', someProperty: true},
{id: 5, make: 'vw', year: '2011', someProperty: true},
{id: 6, make: 'audi', year: '2011', someProperty: true},
{id: 7, make: 'bmw', year: '2010', someProperty: false},
{id: 8, make: 'bmw', year: '2011', someProperty: false},
{id: 9, make: 'bmw', year: '2010', someProperty: true}
];
var requiredFormat = [];
function group()
{
var spt = [], spf = [], y = "0", nexty;
temp.sort
(
function(x, y)
{
return (x.year === y.year ? (x.someProperty > y.someProperty ? -1 : x.someProperty < y.someProperty ? 1 : 0) : x.year < y.year ? -1 : 1);
}
).forEach
(
function(e, i, temp)
{
nexty = (i < temp.length - 1 ? temp[i+1].year : undefined);
(e.someProperty === true ? spt : spf).push(e);
if (e.year !== nexty)
{
y = e.year;
requiredFormat.push({somePropertyTrue: spt, somePropertyFalse: spf, year: y});
spt = [];
spf = [];
y = "0";
}
}
);
}
var temp = cars.slice(0);
group();
console.log("cars: ", cars);
console.log("temp: ", temp);
console.log("requiredFormat: ", requiredFormat);
You also have the fiddle here, to test it. What this code does is:
firstly, create a temporary copy of cars
array, with the help of
slice()
method (maybe you still need the original array?)
secondly, it sorts the temp
array, first by year
, and then by someProperty
, giving you the exact order you'd want in the final result
finally, it "breaks up" the sorted temp
array into the format you require by using 3 variables (spt
, spf
and y
) to store the somePropertyTrue
and somePropertyFalse
arrays, plus the year
, all of which will get "commited" (aka applied to the final result) when the year changes (aka e.year !== nexty
, where nexty
is the year of the next array element, or undefined
if it's the last array element).
You can check each step of the execution by using the console viewer in every browser (you can find it in Developer Tools menu in Chrome, for example). If you want to develop this further, maybe you can use reduce()
to shorten the code - me, I didn't yet master the reduce()
method enough to use it frequently, so I went for the classical method.
This code often uses the ternary operator to shorten the code (quick explanation here, if you're not familiar with it).
Upvotes: 0
Reputation: 1431
const cars = [
{ id: 1, make: 'audi', year: '2010', someProperty: true },
{ id: 2, make: 'bmw', year: '2011', someProperty: false },
{ id: 3, make: 'bmw', year: '2011', someProperty: true },
{ id: 4, make: 'vw', year: '2010', someProperty: true },
{ id: 5, make: 'vw', year: '2011', someProperty: true },
{ id: 6, make: 'audi', year: '2011', someProperty: true },
{ id: 7, make: 'bmw', year: '2010', someProperty: false },
{ id: 8, make: 'bmw', year: '2011', someProperty: false },
{ id: 9, make: 'bmw', year: '2010', someProperty: true }
]
const groupedCars =
cars.reduce((memo, car) => {
memo[car.year] = memo[car.year] || []
memo[car.year].push(car)
return memo
}, [])
.reduce((memo, cars, i) => {
memo[i] = {
somePropertyTrue: cars.filter(car => car.someProperty),
somePropertyFalse: cars.filter(car => !car.someProperty),
year: i
}
return memo
}, [])
.filter(v => v)
document.querySelector('#out').innerHTML = JSON.stringify(groupedCars, null, 4)
<pre id="out" style />
Upvotes: 0
Reputation: 456
You can achieve the desired format using reduce
and a object literal to accumulate the groups:
const result = cars.reduce((state, car) => {
const { year, someProperty } = car;
const group = state[year] || { year, somePropertyTrue: [], somePropertyFalse: [] };
const property = someProperty ? 'somePropertyTrue' : 'somePropertyFalse';
group[property] = [...group[property], car];
return { ...state, [year]: group };
}, {});
Because you are using an object to keep the groups, then the variable result
would be an object. If you want an Array
instead, you can use Object.values
on result
object.
Upvotes: 0
Reputation: 350760
You could do this with an ES6 Map
that you accumulate with reduce
. Here is how that would look:
function transform(cars) {
return [...cars.reduce ( (acc, car) => {
const yearGrp = acc.get(car.year) || {
somePropertyTrue: [],
somePropertyFalse: [],
year: car.year
};
yearGrp['someProperty' + (car.someProperty ? 'True' : 'False')].push(car);
return acc.set(car.year, yearGrp);
}, new Map).values()];
}
var cars = [
{id: 1, make: 'audi', year: '2010', someProperty: true},
{id: 2, make: 'bmw', year: '2011', someProperty: false},
{id: 3, make: 'bmw', year: '2011', someProperty: true},
{id: 4, make: 'vw', year: '2010', someProperty: true},
{id: 5, make: 'vw', year: '2011', someProperty: true},
{id: 6, make: 'audi', year: '2011', someProperty: true},
{id: 7, make: 'bmw', year: '2010', someProperty: false},
{id: 8, make: 'bmw', year: '2011', someProperty: false},
{id: 9, make: 'bmw', year: '2010', someProperty: true}
];
console.log(transform(cars));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 1
Reputation: 114559
You can do a single pass by making your group criteria a function instead of a single field name. Code for that could be:
function groupBy(x, keyfunc) {
let res = {};
x.forEach(obj=>{
let k = keyfunc(obj);
(res[k]||(res[k]=[])).push(obj);
});
return res;
}
Called with let res = groupBy(input, obj=>obj.year+"/"+obj.sameProperty);
.
This will however return a single list per group with keys like "2005/true"
.
To get the structured object you are listing instead you need to do the group operation multiple times and for that a possible solution is recursion:
function groupBy(x, ...keys) {
if (keys.length === 0) {
return x;
} else {
let res = Object.create(null), key = keys[0];
x.forEach(obj=>{
let k = obj[key];
(res[k]||(res[k]=[])).push(obj);
});
let other_keys = keys.slice(1);
Object.keys(res).forEach(k => {
res[k] = groupBy(res[k], ...other_keys);
});
return res;
}
}
calling with x.groupBy("year", "sameProperty")
.
Note also that using an object as a dictionary you're grouping by the conversion to a string of the attribute value; in other words keys values like 2010
, "2010"
and even [2010]
will all end up in the same group.
Upvotes: 0
Reputation: 2290
You could just simply loop again your result, then group the contents.
var groupBy = function(array, key)
{
let result = {};
for (let i = 0; i < array.length; i++)
{
if (array[i][key] in result) {
result[array[i][key]].push(array[i]);
}
else
{
result[array[i][key]] = [array[i]];
}
}
return result;
};
var byYear = groupBy(cars, "year");
var years = Object.keys(byYear);
// loop those byYear Items
for (var i = 0; i < years.length; i++)
{
var year = years[i];
var yearData = byYear[year];
// Then group those values in the inside
byYear[year] = groupBy(yearData, "someProperty");
}
console.log(byYear);
// then byYear will be structured the way you wanted it to be.
hope that helps
Upvotes: 0