Sriram
Sriram

Reputation: 179

Group and map array to different format in JavaScript without using any plugin

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

Answers (6)

Yin Cognyto
Yin Cognyto

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

skylize
skylize

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

pablogq
pablogq

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

trincot
trincot

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

6502
6502

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

masterpreenz
masterpreenz

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

Related Questions