Erik Blomqvist
Erik Blomqvist

Reputation: 481

Create an array from another array's object values that are matching

I'm trying to wrap my head around some more advanced uses of .reduce(), but still have a long way to go. The current issue has left me kind of clueless.

Let's say I have a an array of objects that looks like this:

const people = [
    {
        name: "Henry",
        id: "1934",
        wormsEaten: 4
    },
    {
        name: "Melinda",
        id: "9283",
        wormsEaten: 0
    },
    {
        name: "James",
        id: "1029",
        wormsEaten: 4
    },
    {
        name: "Charles",
        id: "7210",
        wormsEaten: 3
    },
    {
        name: "Sasha",
        id: "4431",
        wormsEaten: 3
    },
]

I now want to create a new array of arrays for each matching wormsEaten value, i.e. an array that looks like this:

[
    [
        {
            name: "Henry",
            id: "1934",
            wormsEaten: 4
        },
        {
            name: "James",
            id: "1029",
            wormsEaten: 4
        }
    ],
    [
        {
            name: "Melinda",
            id: "9283",
            wormsEaten: 0
        }
    ],
    [
        {
            name: "Charles",
            id: "7210",
            wormsEaten: 3
        },
        {
            name: "Sasha",
            id: "4431",
            wormsEaten: 3
        }
    ]
]

My pseudocode-thinking brain says something along the lines of:

people.reduce((accumulator, currentPerson) => {
    if(accumulator.wormsEaten === currentPerson.wormsEaten) {
        return [
          …
        ]
    }
})

But that's roughly how far I get. I feel like I have a long way to go with this type of thinking and would be thankful if someone could help me in the right direction.

Upvotes: 0

Views: 64

Answers (2)

pilchard
pilchard

Reputation: 12919

Sorting will be more efficient after grouping since you will have a shorter array.

const people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }];

const result = {};
for (const person of people) {
  (result[person.wormsEaten] ??= []).push({ ...person });
}

const sortedResult = Object.values(result).sort(([a], [b]) => a.wormsEaten - b.wormsEaten)

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

If you must sort before grouping you can maintain your sorted array ordering through the reduce by accumulating into a Map, which maintains insertion order; into an Array, which necessarily enforces insertion order; or keep using an object, but force all your properties to be strings.

Map

const
  people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }],
  sorted = people.sort((a, b) => a.wormsEaten - b.wormsEaten),

  result = sorted.reduce((map, person) => {
    if (!map.has(person.wormsEaten)) {
      map.set(person.wormsEaten, []);
    }
    map.get(person.wormsEaten).push({ ...person });

    return map;
  }, new Map)

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

Array

const
  people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }],
  sorted = people.sort((a, b) => a.wormsEaten - b.wormsEaten),

  result = sorted.reduce((arr, person) => {
    let groupIndex = arr.findIndex(_arr => _arr[0].wormsEaten === person.wormsEaten);

    if (groupIndex === -1) {
      groupIndex = arr.push([]) - 1;
    }

    arr[groupIndex].push({ ...person });

    return arr;
  }, []);

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

Alternative implementation since you have already sorted the array by the property you will be grouping by.

const
  people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }],
  sorted = people.sort((a, b) => a.wormsEaten - b.wormsEaten),

  result = sorted.reduce((arr, person) => {
    if (person.wormsEaten !== arr[arr.length - 1]?.[0].wormsEaten) {
      arr.push([])
    }

    arr[arr.length - 1].push({ ...person })

    return arr;
  }, []);

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

Object with string properties

const
  people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }],
  sorted = people.sort((a, b) => a.wormsEaten - b.wormsEaten),

  result = sorted.reduce((obj, person) => {
    (obj[`_${person.wormsEaten}`] || (obj[`_${person.wormsEaten}`] = [])).push({ ...person });

    return obj;
  }, {})

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


A reduce() call like this is equivalent to a standard for loop with an internally passed accumulator. Here illustrated with a for..of loop and using the nullish logical assignment operator (??=).

const
  people = [{ name: "Melinda", id: "9283", wormsEaten: 0 }, { name: "James", id: "1029", wormsEaten: 4 }, { name: "Charles", id: "7210", wormsEaten: 3 }, { name: "Sasha", id: "4431", wormsEaten: 3 }, { name: "Henry", id: "1934", wormsEaten: -9 }],
  sorted = people.sort((a, b) => a.wormsEaten - b.wormsEaten);

const result = {};
for (const person of people) {
  (result[`_${person.wormsEaten}`] ??= []).push({ ...person });
}

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

Upvotes: 0

User863
User863

Reputation: 20039

By grouping with wormsEaten

const people = [{name:"Henry",id:"1934",wormsEaten:4},{name:"Melinda",id:"9283",wormsEaten:0},{name:"James",id:"1029",wormsEaten:4},{name:"Charles",id:"7210",wormsEaten:3},{name:"Sasha",id:"4431",wormsEaten:3}]

const result = people.reduce((accumulator, currentPerson) => {
    accumulator[currentPerson.wormsEaten] = accumulator[currentPerson.wormsEaten] || []
    accumulator[currentPerson.wormsEaten].push(currentPerson)
    
    return accumulator
}, {})

console.log(Object.values(result))

UPDATE to preserve order

const people = [{name:"Henry",id:"1934",wormsEaten:4},{name:"Melinda",id:"9283",wormsEaten:0},{name:"James",id:"1029",wormsEaten:4},{name:"Charles",id:"7210",wormsEaten:3},{name:"Sasha",id:"4431",wormsEaten:3}]

const result = people.reduce((accumulator, currentPerson) => {
    let key = 'wormsEaten' + currentPerson.wormsEaten // to preserve order
    accumulator[key] = accumulator[key] || []
    accumulator[key].push(currentPerson)
    
    return accumulator
}, {})

console.log(Object.values(result))

Upvotes: 2

Related Questions