-2

I have two arrays like this:

let a = [{id: 1, price: 50}, {id: 2, price: 30}, {id: 1, price: 40}, {id: null, price: 80}];
let b = [{id: 1, name: "apple"}, {id: 2, name: "orange"}];

Now I want a result like this:

result = [ {name: "apple", prices: [{id: 1, price: 50}, {id: 1, price: 40}]}, {name: "orange", prices: [{id: 2, price: 30}]}, {name: "others", prices: [{id: null, price: 80}]}] 

I want to map the elements of the array a to the name of the second array b on the basis of their ids.

Nitesh Ranjan
  • 1,221
  • 1
  • 13
  • 13
  • is the result for apple's prices supposed to be `[{id: 1, price: 50}, {id: 1, price: 40}]` instead? – Shawn Andrews Nov 24 '18 at 21:36
  • this is just an example of the real problem i have. i did give a bad example but yes i want exactly what i wrote. – Nitesh Ranjan Nov 24 '18 at 21:37
  • im confused by the result. you say you want `a` to map to `b` on the basis of their ids, but `{id: 1, price: 40}` in `a` maps to "apple" but its not in the apple object in your result? – Shawn Andrews Nov 24 '18 at 21:45
  • Why would you want to repeat the id with *each* price? Certainly the id will be the same for the prices that appear in the same array. It seems more appropriate to avoid this repetition, and put the id at the name level in your output structure. – trincot Nov 24 '18 at 22:12
  • Any feed-back on my comment, and on the answers below? – trincot Nov 25 '18 at 19:29

4 Answers4

1

Here's an approach that using reduce to build a lookup set and avoid repeated searches in b. Another reduction pass builds the result arrays by name using the lookup table. Lastly, map is used to format the result.

Time complexity is linear (three passes with a lot of constant time object lookups).

let a = [{id: 1, price: 50}, {id: 2, price: 30}, {id: 1, price: 40}, {id: null, price: 80}];
let b = [{id: 1, name: "apple"}, {id: 2, name: "orange"}];

const lookup = b.reduce((a, e) => {
  a[e.id] = e.name;
  return a;
}, {});

const result = Object.entries(
  a.reduce((a, e) => {
    const key = lookup[e.id] || "others";
    
    if (!(key in a)) {
      a[key] = [];
    }

    a[key].push(e);
    return a;
  }, {})
).map(e => ({name: e[0], prices: e[1]}));

console.log(result);
ggorlen
  • 44,755
  • 7
  • 76
  • 106
1

It would be more logical to not repeat the id in the prices part of the result, since the id belongs with the name.

I would suggest using a temporary map (for efficiency):

let a = [{id: 1, price: 50}, {id: 2, price: 30}, {id: 1, price: 40}, {id: null, price: 80}];
let b = [{id: 1, name: "apple"}, {id: 2, name: "orange"}];

const map = new Map(b.map(o => [o.id, Object.assign(o, { prices: [] })]))
            .set(null, {id: null, name: "others", prices: []});

a.forEach(o => map.get(o.id).prices.push(o.price));

const result = [...map.values()];

console.log(result);
trincot
  • 317,000
  • 35
  • 244
  • 286
1

Yes, you can do it simply with a map and a filter

let a = [{id: 1, price: 50}, {id: 2, price: 30}, {id: 1, price: 40}, {id: null, price: 80}];
let b = [{id: 1, name: "apple"}, {id: 2, name: "orange"}];

b.map(({ name, id }) => ({
  name,
  id,
  prices: a.filter(item => item.id === id).map(({ price }) => price)
}));
Andy Ray
  • 30,372
  • 14
  • 101
  • 138
1

You can do this with a single Array.reduce and Object.values if you start by combining the 2 arrays together:

let a = [{id: 1, price: 50}, {id: 2, price: 30}, {id: 1, price: 40}, {id: null, price: 80}];
let b = [{id: 1, name: "apple"}, {id: 2, name: "orange"}];

const result = Object.values([...b, ...a].reduce((r, c) => {
  if ('name' in c || c.id == null)
    r[c.id || 'others'] = ({name: c.name || 'others', prices: []})

  if ('name' in c)
    return r
  else if (c.id != null)
    r[c.id].prices.push(c)
  else
    r['others'].prices.push(c)
  return r
}, {}))

console.log(result)

The idea is to start with the one containing the names so the grouping creates first the object groupings and then just fill the group arrays.

Akrion
  • 18,117
  • 1
  • 34
  • 54