0

What is the best way to Group an array of objects according to the keys in vanilla javascript, lets say I have 10000 records, this is the sample object

[
 {
  company: "TATA",
  car: "TATA Indica",
  color: "Blue"
 },
 {
  company: "TATA",
  car: "TATA Indica",
  color: "Black"
 },
 {
  company: "TATA",
  car: "Safari",
  color: "Blue"
 },
 {
   "company": "Suzuki",
    car: "",
    color: ""
 }
]

and the expected output is

{
   "company": ["TATA", "Suzuki"],
   "car": ["TATA Indica", "Safari"],
   "color": ["Blue", "Black"]
}
4
  • Does the order important here for end result arrays like company, car, or color? Commented Sep 3, 2021 at 7:26
  • You should have provided your attempt at it Commented Sep 3, 2021 at 7:35
  • You have accepted the slowest version. jsben.ch/kLhTZ Commented Sep 3, 2021 at 8:05
  • pilchard have now the fastest solution! jsben.ch/4KKgm Commented Sep 3, 2021 at 9:28

4 Answers 4

1

You can use reduce here, and to optimize it you can use Map here.

const arr = [{
    company: "TATA",
    car: "TATA Indica",
    color: "Blue",
  },
  {
    company: "TATA",
    car: "TATA Indica",
    color: "Black",
  },
  {
    company: "TATA",
    car: "Safari",
    color: "Blue",
  },
  {
    company: "Suzuki",
    car: "",
    color: "",
  },
];

const result = arr.reduce((acc, curr) => {
  Object.keys(curr).forEach((k) => {
    if (curr[k]) {
      if (!acc[k]) acc[k] = new Set();
      else acc[k].add(curr[k]);
    }
  });
  return acc;
}, {});

Object.keys(result).forEach((k) => (result[k] = [...result[k]]));
console.log(result);

Sign up to request clarification or add additional context in comments.

Comments

1

For primitive values you can easily use the combination of Set and Map. You use Map for the keys and Set for the values.

const companies = ["TATA", "Suzuki", "Škoda", ""];
const cars = ["TATA Indica", "Safari", "Fabia", ""];
const colors = ["Blue", "Black", "Red", "Yellow", ""]
const arr = Array.from({length: 1000}, (v, i) => ({
  company: companies[i % companies.length],
  car: cars[i % cars.length],
  color: colors[i % colors.length]
}));

console.time('Operation');
const map = new Map();
for(let item of arr) {
  for(let key of Object.keys(item)){
    let keySet = map.get(key);
    if(!keySet){
      keySet = new Set();
      map.set(key, keySet);
    }
    const value = item[key];
    if(value !== '') {
      keySet.add(item[key]);
    }
  }
}
const result = {};
for(let key of map.keys()) {
  result[key] = Array.from(map.get(key));
}
console.timeEnd('Operation');
console.log(result);

Comments

1

It looks like you are using data that has a stable shape throughout. In this case you can declare the shape of the result object in advance, and then simply accumulate the data into it only checking for non-empty values. Here using Map and Set for the accumulation and then mapping it to the final result object.

const data = [{ company: 'TATA', car: 'TATA Indica', color: 'Blue' }, { company: 'TATA', car: 'TATA Indica', color: 'Black' }, { company: 'TATA', car: 'Safari', color: 'Blue' }, { company: 'Suzuki', car: '', color: '' },];

const map = new Map(Object.keys(data[0]).map((k) => [k, new Set()]));

for (const d of data) {
  for (const k of map.keys()) {
    if (d[k] !== '') {
      map.get(k).add(d[k]);
    }
  }
}

const result = {};
for (const k of map.keys()) {
  result[k] = Array.from(map.get(k));
}

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Curiously, what seems like the most naive implementation actually proves fairly performant (per @Totati's benchmark). Serial map().filter() calls.

const data = [{ company: 'TATA', car: 'TATA Indica', color: 'Blue' }, { company: 'TATA', car: 'TATA Indica', color: 'Black' }, { company: 'TATA', car: 'Safari', color: 'Blue' }, { company: 'Suzuki', car: '', color: '' },];

const result = {};

for (const k of Object.keys(data[0])) {
  result[k] = Array.from(
    new Set(data.map(({ [k]: key }) => key).filter((n) => n !== ''))
  );
}

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

1 Comment

Thanks @Totati (though you left my real answer off ;) https://jsben.ch/4KKgm)
0

You could use Set to store the unique values for each key and convert them back to array later

const data = [
  {
    company: "TATA",
    car: "TATA Indica",
    color: "Blue",
  },
  {
    company: "TATA",
    car: "TATA Indica",
    color: "Black",
  },
  {
    company: "TATA",
    car: "Safari",
    color: "Blue",
  },
  {
    company: "Suzuki",
    car: "",
    color: "",
  },
]

let res = data.reduce((acc, el) => {
  for (const [key, value] of Object.entries(el)) {
    if (!value) continue

    if (key in acc) {
      const set = new Set(acc[key])
      set.add(value)
      acc[key] = Array.from(set)
    } else {
      acc[key] = [value]
    }
  }

  return acc
}, {})

console.log(res)

1 Comment

Well, this is the slowest version.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.