3

I have a collection like this:

const data = [
{index: 1, number: 's1', uniqId: '123', city: 'LA'},
{index: 2, number: 's2', uniqId: '321', city: 'NY'},
{index: 3, number: 's3', uniqId: '123', city: 'LA'},
{index: 4, number: 's4', uniqId: '111', city: 'TX'},
{index: 5, number: 's5', uniqId: '321', city: 'NY'}
]

I'd like to group it to have below result:

const data = [
{index: 1, numbers: ['s1', 's3'], uniqId: '123', city: 'LA'},
{index: 2, numbers: ['s2', 's5'], uniqId: '321', city: 'NY'},
{index: 3, number: 's4', uniqId: '111', city: 'TX'},
]

I've got a solution but I believe that it can be achieved in a more elegant way. I can use ramda only but preferred is vanilla solution. Here's my solution:

 return Object.values(
    data.reduce((r, e) => {
      const key = `${e.uniqId}|${e.city}`;
      if (!r[key]) {
        r[key] = e;
        if (r[key].numbers && !isEmpty(r[key].numbers)) {
          r[key].numbers.push(e.number);
        } else {
          r[key].numbers = [];
          r[key].numbers.push(e.number);
        }
      } else if (r[key].numbers && !isEmpty(r[key].numbers)) {
        r[key].numbers.push(e.number);
      } else {
        r[key].numbers = [];
        r[key].numbers.push(e.number);
      }
      return r;
    }, {}),
  ).map((item, index) => ({ ...item, index: index }));
2
  • Does the index imply some kind of sorting? Commented Dec 17, 2019 at 17:49
  • Does this answer your question? Group array items using object Commented Dec 17, 2019 at 19:23

6 Answers 6

2

You're doing way more work than you have to in your reducer

const data = [
{index: 1, number: 's1', uniqId: '123', city: 'LA'},
{index: 2, number: 's2', uniqId: '321', city: 'NY'},
{index: 3, number: 's3', uniqId: '123', city: 'LA'},
{index: 4, number: 's4', uniqId: '111', city: 'TX'},
{index: 5, number: 's5', uniqId: '321', city: 'NY'}
]

const reduced = data.reduce((acc, e) => {
  const key = `${e.uniqId}|${e.city}`;
  if (! (key in acc)) {
    acc[key] = Object.assign({}, e);
    delete acc[key]['number'];
    acc[key]['numbers'] = [];
  }
  acc[key]['numbers'].push(e.number);
  return acc;
}, {});

console.log(Object.values(reduced));
Sign up to request clarification or add additional context in comments.

Comments

0

An alternate reducer function

data.reduce((acc, curr) => {
  const existingIdRow = acc.find(existingElement => existingElement.uniqId === curr.uniqId);
  if(existingIdRow && existingIdRow.numbers)
    existingIdRow.numbers.push(curr.number);
  else {
    const { uniqId, city } = curr;
    acc.push({index: acc.length + 1, numbers: [curr.number], uniqId, city});
  }
  return acc
}, [])

Comments

0

well yes, you can do it using just one reducer and a condition to loop, so this will do it faster than getting the keys of the objects and then loopping through it.

const data = [
  {index: 1, number: 's1', uniqId: '123', city: 'LA'},
  {index: 2, number: 's2', uniqId: '321', city: 'NY'},
  {index: 3, number: 's3', uniqId: '123', city: 'LA'},
  {index: 4, number: 's4', uniqId: '111', city: 'TX'},
  {index: 5, number: 's5', uniqId: '321', city: 'NY'}
]


const reducer = (accum, cv, currentIndex, source) => {
  const hasValue = accum.some(entry => entry.uniqId === cv.uniqId);
  // we already proccessed it.
  if (hasValue) return accum;

  // we create an object with the desired structure.
  const {
    index,
    uniqId,
    city,
    number
  } = cv;
  let newObj = {
    index,
    uniqId,
    city,
    numbers: [number]
  };

  //now lets fill the numbers :)
  source.forEach((v, index) => {
    //index !== currentIndex &&
    if (index !== currentIndex && v.uniqId === uniqId) {
      newObj['numbers'].push(v.number);
    }
  })

  return [...accum, newObj];

}

const result = data.reduce(reducer, []);

console.log(result)

Comments

0

Here's a really concise take on the problem using the first object in the array as a map and object destructuring:

const data = [
{index: 1, number: 's1', uniqId: '123', city: 'LA'},
{index: 2, number: 's2', uniqId: '321', city: 'NY'},
{index: 3, number: 's3', uniqId: '123', city: 'LA'},
{index: 4, number: 's4', uniqId: '111', city: 'TX'},
{index: 5, number: 's5', uniqId: '321', city: 'NY'}
]

const result = data.reduce((acc, {number,uniqId,city}) => {
  if (!acc[0][uniqId]) {
    acc.push(acc[0][uniqId] = {index: acc.length, numbers: [], uniqId, city});
  }
  acc[0][uniqId].numbers.push(number);
  return acc;
}, [{}]).slice(1);

console.log(result);

Comments

0

Typically you don't want to have two different properties listing similar data, so the first thing I did was create a map() function to change all number props to numbers and made them single item arrays. Then I used the reduce() function to group the objs with a uniqId together. I would have left it at that, but since you wanted a result with either number or numbers depending on the object, I wrote a simple map() func at the end to convert back to this format.

const data = [
{index: 1, number: 's1', uniqId: '123', city: 'LA'},
{index: 2, number: 's2', uniqId: '321', city: 'NY'},
{index: 3, number: 's3', uniqId: '123', city: 'LA'},
{index: 4, number: 's4', uniqId: '111', city: 'TX'},
{index: 5, number: 's5', uniqId: '321', city: 'NY'}
]


let res = data.map((el) => {
   el.numbers = [el.number]
   delete el.number
   return el
}).reduce((acc,cur) => {
   let ids = acc.map(obj => obj.uniqId)
   let io = ids.indexOf(cur.uniqId)
   if(io > -1){
      acc[io].numbers.push(cur.numbers[0])
   }else{
      acc.push(cur)
   }
   
   return acc
},[])

console.log(res)

res = res.map(el => {
   if(el.numbers.length <= 1){
      el.number = el.numbers[0]
      delete el.numbers
   }
   return el
})

console.log(res)

Comments

0

Here's a simple way to do this with a Map, and conforms to your desired output logically - alas, the numbers property is out of position compared to your desired output.

If that is important, I leave that for you to sort out ;)

const data = [
  { index: 1, number: 's1', uniqId: '123', city: 'LA' },
  { index: 2, number: 's2', uniqId: '321', city: 'NY' },
  { index: 3, number: 's3', uniqId: '123', city: 'LA' },
  { index: 4, number: 's4', uniqId: '111', city: 'TX' },
  { index: 5, number: 's5', uniqId: '321', city: 'NY' }
];

const reducer = (acc, e, idx, arr) => {
      const key = `${e.uniqId}|${e.city}`;
      let value = acc.get(key);
      if (value === undefined) {  
      	value = Object.assign({},e);    
        acc.set(key, value);
        value.index = acc.size;
      } else {
        if('number' in value) {
      	  value.numbers = [value.number]
          delete value.number;
    	}
        value.numbers.push(e.number);
      }
      if (++idx === arr.length) {
        return Array.from(acc.values());
      }
      return acc;
    };
    
const result = data.reduce(reducer, new Map());
document.getElementById('result').innerText = JSON.stringify(result);
<code id="result"></code>

Comments

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.