Chandrika
Chandrika

Reputation: 141

Best way to convert and array of objects into a simplistic array

I have an array of objects. Basically, each object has 2 keys named id and value. Now I have to convert this array into an another array but with respect to ids.

Eg

const arr1 = [{
    "month": "April",
    "values": [{
       "id": "rice",
       "value": 10
    },
    {
       "id": "wheat",
       "value": 20
    },
    {
       "id": "corn",
       "value": 30
    }]
},{
    "month": "May",
    "values": [{
       "id": "rice",
       "value": 40
    },
    {
       "id": "wheat",
       "value": 50
    },
    {
       "id": "corn",
       "value": 60
    }]
},{
    "month": "June",
    "values": [{
       "id": "rice",
       "value": 70
    },
    {
       "id": "wheat",
       "value": 80
    },
    {
       "id": "corn",
       "value": 90
    }]
}]

I want to take out values and make it something like

const arr2 = [{
   id: "rice",
   values: [10,40,70],
},{
   id: "wheat",
   values: [20,50,80],
},{
   id: "corn",
   values: [30,60,90],
}]

arranged in order of dates.

What I have tried

const ids = arr1[0].values.map(value => value.id);
const arr2 = ids.map(id => {
    return {
        id,
        values:[]
    }
}

I am unable to think of how to fill the values. The most obvious way will require 3 loops which should not be a good idea.

What should i do?

Upvotes: 0

Views: 62

Answers (4)

pilchard
pilchard

Reputation: 12919

This really just a 'group by' operation with a nested array. As such, you can handle it in a single reduce() call, with a nested loop over each values array.

const arr1 = [ { month: 'April', values: [ { id: 'rice', value: 10, }, { id: 'wheat', value: 20, }, { id: 'corn', value: 30, }, ], }, { month: 'May', values: [ { id: 'rice', value: 40, }, { id: 'wheat', value: 50, }, { id: 'corn', value: 60, }, ], }, { month: 'June', values: [ { id: 'rice', value: 70, }, { id: 'wheat', value: 80, }, { id: 'corn', value: 90, }, ], }, ];

const result = Object.values(
  arr1.reduce((a, { values }) => {
    for (const { id, value } of values) {
      (a[id] ??= { id, values: [] }).values.push(value);
    }

    return a;
  }, {})
);

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

Or using nested reduce() calls passing a single Map instance to both.

const arr1 = [ { month: 'April', values: [ { id: 'rice', value: 10, }, { id: 'wheat', value: 20, }, { id: 'corn', value: 30, }, ], }, { month: 'May', values: [ { id: 'rice', value: 40, }, { id: 'wheat', value: 50, }, { id: 'corn', value: 60, }, ], }, { month: 'June', values: [ { id: 'rice', value: 70, }, { id: 'wheat', value: 80, }, { id: 'corn', value: 90, }, ], }, ];

const result = Array.from(
  arr1.reduce(
    (a, { values }) => 
      values.reduce((b, { id, value }) => b.set(id, [...(b.get(id) ?? []), value])
        , a)
    , new Map()
  ),
  (e) => Object.fromEntries([e])
);

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

Upvotes: 0

Ori Drori
Ori Drori

Reputation: 191976

Get an array of values using Array.flatMap(), and then reduce to a Map, and transform to an array of object by converting the Map using Array.from():

const fn = months => 
  Array.from(months 
    .flatMap(({ values }) => values)
    .reduce(
      (acc, o) => acc.set(o.id, [...(acc.get(o.id) ?? []), o.value]),
      new Map()
    ),
    ([id, values]) => ({ id, values })
  )

const months = [{month: "April", values: [{id: "rice", value: 10}, {id: "wheat", value: 20}, {id: "corn", value: 30}]}, {month: "May", values: [{id: "rice", value: 40}, {id: "wheat", value: 50}, {id: "corn", value: 60}]}, {month: "June", values: [{id: "rice", value: 70}, {id: "wheat", value: 80}, {id: "corn", value: 90}]}]

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

Upvotes: 0

Scott Sauyet
Scott Sauyet

Reputation: 50797

Here's one approach:

const extractValues = months => 
  Object .entries (months 
    .flatMap (({values}) => values)
    .reduce ((a, {id, value}) => ({...a, [id]: [...(a [id] || []), value]}), {})
  ) .map (([id, values]) => ({id, values}))

const months = [{month: "April", values: [{id: "rice", value: 10}, {id: "wheat", value: 20}, {id: "corn", value: 30}]}, {month: "May", values: [{id: "rice", value: 40}, {id: "wheat", value: 50}, {id: "corn", value: 60}]}, {month: "June", values: [{id: "rice", value: 70}, {id: "wheat", value: 80}, {id: "corn", value: 90}]}]

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

We start by flat-mapping the original array to combine all the value nodes in a single array, so it will contain rice/10, wheat/20, corn/30, rice/40, ..., corn/90, in objects like your original: {id: 'rice', value: 10}.

Then with reduce, we combine those into

{
  rice: [10, 40, 70],
  wheat: [20, 50, 80],
  corn: [30, 60, 90]
}

We use Object.entries to extract that into

[
  ['rice', [10, 40, 70]],
  ['wheat', [20, 50, 80]],
  ['corn', [30, 60, 90]]
]

and then we map over them, converting this into your expected output structure.

We retain the order of the inputs, and don't try to resort by your month. That would not be much more work, but it's a reasonable guess that the data will already be sorted that way.

Update

I was curious about the possibility that the data is less consistent, and thought I'd rewrite in a way that handled that. In this version, we also have one month that has soybean records, and June is in the array before May. So we need to sort on the months, and capture all the ids we might need in any of them. It looks like this:

const byMonth = ((months) => (a, b) => 
  (months .indexOf (a .month) || 13) - (months .indexOf (b .month) || 13) 
)(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'])

const extract = (
  xs,
  months = [... xs] .sort (byMonth),
  ids = [... new Set (months .flatMap (({values}) => values .map (({id}) => id)))]
) => ids .map ((id) => ({
  id,
  values: months .map (m => (m .values .find (v => v.id == id) || {value: null}) .value)
}))

const months = [{month: "April", values: [{id: "rice", value: 10}, {id: "wheat", value: 20}, {id: "corn", value: 30}]}, {month: "June", values: [{id: "rice", value: 40}, {id: "wheat", value: 50}, {id: "corn", value: 60}]}, {month: "May", values: [{id: "rice", value: 70}, {id: "wheat", value: 80}, {id: "soybeans", value: 42}, {id: "corn", value: 90}]}]

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

Note that the output now has some nulls to note that there were no soybean records in April or June, only in May:

  {
    id: "soybeans",
    values: [null, 42, null]
  }

This sort of working around inconsistent data is a large part of my job, and of many developers I know.

Upvotes: 1

Alexey Zelenin
Alexey Zelenin

Reputation: 730

const arr2 = arr1.flatMap(x => x.values);
arr2.map(x => ({ 
    id: x.id, 
    value: arr2
        .filter(y => y.id === x.id)
        .map(y => y.value) 
}));

Upvotes: 0

Related Questions