0

I'm stuck at transforming a data structure:

let d = [
  { no: 1, score: 7000 },
  { no: 2, score: 10000 },
  [
    { no: 1, score: 8500 },
    { no: 2, score: 6500 }
  ]
];

    
d = d.reduce((accum, o) => {
   
}, [])

How can I produce this?

[{name: 'no 1', score: [7000, 8500]}, {name: 'no 2', score: [10000, 6500]}]
AbsoluteBeginner
  • 2,160
  • 3
  • 11
  • 21
Nadiely Jade
  • 195
  • 2
  • 9
  • If you fix the result/syntax error it will be a dupe of: [How to group an array of objects by key](https://stackoverflow.com/questions/40774697/how-to-group-an-array-of-objects-by-key) – Andreas Dec 29 '20 at 09:50
  • @Andreas it's fixed. No it's not the same, my raw data is an array of array – Nadiely Jade Dec 29 '20 at 09:51
  • It is - with an additional [`Array.prototype.flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) call before the actual grouping – Andreas Dec 29 '20 at 09:52

8 Answers8

2

Here is one way to do it with simple reduce,

const result = d.flat().reduce((acc: {name: string, score: number[]}[], curr) => {
  const { no, score } = curr;
  let item = acc.find(a => a.name === `no ${no}`);
  if (!item) {
    item = { name: `no ${no}`, score: []};
    acc.push(item);
  }

  item.score.push(score);
  return acc;
    
}, []);

console.log(result)
Sandeep
  • 20,908
  • 7
  • 66
  • 106
  • the most simple solution, clean. Thanks! – Nadiely Jade Dec 29 '20 at 12:08
  • @NadielyJade Be careful, the `flat` expects as parameter `depth` (1 by default). So the solution above is perfect if you know the depth of the array. If the depth is not known you will need a recursive call of reducer. – Vitaliy Javurek Dec 29 '20 at 12:39
0

In your case, you not only need to flatten the list, but also group by no property.

For flatting, you can use Array.prototype.flat(). It's quite a new feature, so if you don't use polyfills, you probably can't use it. So you can check for alternative implementations.

For grouping, you can reduce to the object where the key is no property. Note, that if multiple no properties exist, you need to save an array of all score values.

Example:

const d = [{ no: 1, score: 7000 },
    { no: 2, score: 10000 },
   [ { no: 1, score: 8500 },
    { no: 2, score: 6500 }]]


const grouped = d.flat().reduce((prev, cur) => {
    if (cur.no in prev) {
        prev[cur.no].score.push(cur.score)
    } else {
        prev[cur.no] = {
            name: 'no ' + cur.no,
            score: [cur.score]
        }
    }
    return prev
}, {})

console.log(Object.values(grouped))

In the example we use modifications. It is possible to do it without modifications - return a new copy during each reduction iteration. However, depending on array size, there can be performance issues. Also, it's safe to do modifications, because we create a new object in this case.

KiraLT
  • 2,385
  • 1
  • 24
  • 36
0

let d = [{ no: 1, score: 7000 },
    { no: 2, score: 10000 },
   [ { no: 1, score: 8500 },
    { no: 2, score: 6500 }]]
    
    const result=d.flat().reduce((acc,curr)=>{
    if(acc[curr.no]){
      acc[curr.no].score.push(curr.score)
    } else {
      const keys=Object.keys(curr)
      acc[curr.no]={ name: keys[0]+ ' '+curr.no, score:[curr.score]}
    }
    return acc;
    },{})
console.log(Object.values(result))
Nilesh Patel
  • 3,193
  • 6
  • 12
0

Array.prototype.flat() call before the actual grouping, then use reduce function create the result.

let d = [{
    no: 1,
    score: 7000
},
{
    no: 2,
    score: 10000
},
[{
        no: 1,
        score: 8500
    },
    {
        no: 2,
        score: 6500
    }
]
]

const result = d.flat().reduce((result, element) => {
const key = element.no;
if (!result[key]) {
    result[key] = {
        name: `no ${key}`,
        score: []
    }
}

result[key].score.push(element.score);
return result;
}, {})
console.log(Object.values(result))
Rahul Sharma
  • 9,534
  • 1
  • 15
  • 37
0

Here's a sleek functional solution for typescript, since you included the tag-

const arr = [
    { no: 1, score: 7000 },
    { no: 2, score: 10000 },
    [
        { no: 1, score: 8500 },
        { no: 2, score: 6500 },
    ],
];

const result = Object.entries(
    arr.flat().reduce((accum: { [key: number]: number[] }, el: { no: number; score: number }) => {
        accum[el.no] = (accum[el.no] ?? []).concat(el.score);
        return accum;
    }, {})
).map(([num, scores]) => ({ no: Number(num), scores: scores }));

console.log(result);

Result-

[
  { no: 1, scores: [ 7000, 8500 ] },
  { no: 2, scores: [ 10000, 6500 ] }
]

This flattens the inner arrays first using Array.prototype.flat. Then it uses reduce to construct an object that has the no values as keys and score values as an array of values.

In the end, the reduce results in { 1: [7000, 8500], 2: [10000, 6500] } - turn that into entries using Object.entries to get [['1', [7000, 8500]], ['2', [10000, 6500]]]

Finally, map over the entries to turn the ['1', [7000, 8500]] format into { no: 1, scores: [ 7000, 8500 ] } format and you're done!

Chase
  • 5,315
  • 2
  • 15
  • 41
0

You could take a dynamic approach which groups by the given key and takes all other properties for a new array.

const
    groupBy = key => (r, value) => {
        if (Array.isArray(value)) return value.reduce(group, r);
        const { [key]: _, ...o } = value;
        Object.entries(o).forEach(([k, v]) => ((r[_] ??= { [key]: _ })[k] ??= []).push(v));
        return r;
    }
    data = [{ no: 1, score: 7000 }, { no: 2, score: 10000 }, [{ no: 1, score: 8500 }, { no: 2, score: 6500 }]],
    group = groupBy('no'),
    result = Object.values(data.reduce(group, {}));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You can use Array.prototype.flat()

or if you have any array like,

let d = [
  { no: 1, score: 7000 },
  { no: 2, score: 10000 },
  [
    { no: 1, score: 8500 },
    { no: 2, score: 6500 }
  ]
];

and then use d.flat()

Hari Kishore
  • 2,201
  • 2
  • 19
  • 28
0

Try this:

let d = [
    { no: 1, score: 7000 },
    { no: 2, score: 10000 },
    [
        { no: 1, score: 8500 },
        { no: 2, score: 6500 }
    ]
]


const reducer = (arr, start = []) => arr.reduce((acc, next) => {
    if (Array.isArray(next)) return reducer(next, acc);
    for (const value of acc) {
        if (value.name === `no ${next.no}`) {
            value.score = [...value.score, next.score];
            return acc;
        }
    }
    
    return [...acc, {
        name: `no ${next.no}`,
        score: [next.score]
    }];
}, start);

console.log(reducer(d));