2

So i've been struggling for a few hours now. Here's what I'm trying to do.

I have an array of objects:

initArr = [
  {id: 5, time: 100, download: true},
  {id: 2, time: 50, download: false},
  {id: 3, time: 1000, download: true},
  {id: 5, time: 50, download: true},
  {id: 5, time: 550, download: false},
  {id: 2, time: 1500, download: true}
]

some objects have the same id, I want to merge them together.
- but when merging i want to sum the time value of both merged object .
- if one of the two merged objects has download: false, the merged object should have false, otherwise true.

Here's what I have so far (I did not start yet even considering the download key):

const mergedArr= [];

initArr.map(obj => obj['id'])
     .map((id, i, arr) => {
        if (arr.indexOf(id) === i) {
            mergedArr.push(initArr[i]);
        } else { 
          const mergeIndex = mergedArr.map( x => x.id).indexOf(id);
          mergedArr[mergeIndex].playTime +=initArr[arr.indexOf(id)].playTime;
        }
    return mergedArr
});

I'd love so inputs or hints :)

2
  • 1
    Is preserving the order important? Commented Jul 16, 2019 at 14:24
  • yes ! I should have mentioned that :) Commented Jul 16, 2019 at 14:55

6 Answers 6

2

You could reduce the array. Create an accumulator object with each id as key and the merged object as value. If the id already exists, update time and download with current object. Else, add the current id as key and set a copy of the current object as it's value. Then use Object.values() to get an array of values from this accumulator object.

const initArr = [
  { id: 5, time: 100, download: true },
  { id: 2, time: 50, download: false },
  { id: 3, time: 1000, download: true },
  { id: 5, time: 50, download: true },
  { id: 5, time: 550, download: false },
  { id: 2, time: 1500, download: true }
]

const merged = initArr.reduce((acc, o) => {
  if (acc[o.id]) {
    acc[o.id].time += o.time;
    acc[o.id].download = acc[o.id].download && o.download;
  } else
    acc[o.id] = { ...o };

  return acc;
}, {})

console.log(Object.values(merged))

Sign up to request clarification or add additional context in comments.

8 Comments

This case is not fit for an object accumulator, since the ids are numeric. You can see that the order has changed (id: 2 is the 1st item).
Can't you just use r[o.id] = o ?
@Kobe If not cloned, when r[o.id].time += o.time is done, it will mutate the original array's object
@OriDrori Why would an object not fit here? It works as well as an array or a Map. Order in the output is hardly relevant.
I should have specified indeed that the order matter here ! but thanks a lot for the detailed explanation !
|
1

Reduce the array to a Map by the object's id, and then convert back to array using Map.values() and Array.from() (or by spreading into an array).

Note: in this case using Map as an accumulator is better than using an object, since the id property is numeric. In ES6 objects usually maintain the order of insertion, unless the properties are numeric. When an object with numeric property keys is convert to an array, the numeric properties come first, and they are ordered by their value. On the other hand, a Map always maintains the order of insertion.

const initArr = [
  {id: 5, time: 100, download: true},
  {id: 2, time: 50, download: false},
  {id: 3, time: 1000, download: true},
  {id: 5, time: 50, download: true},
  {id: 5, time: 550, download: false},
  {id: 2, time: 1500, download: true}
]

const result = Array.from(initArr.reduce((r, o) => {
  if(!r.has(o.id)) r.set(o.id, { ...o });
  else {
    const current = r.get(o.id);
    current.time += o.time;
    current.download = current.download && o.download
  }
  
  return r;
}, new Map).values());

console.log(result);

2 Comments

+1 thanks !! With the order being important in my case, this solution is very helpful. I'm not sure I quite understand it completely yet. I mean I get the reduce part. I've never used new Map though. I'll try to read on that. thanks again
You're welcome :) I'll add a link to info about Map as well.
1

Try this, supports download option as well

const arr = [
  {id: 5, time: 100, download: true},
  {id: 2, time: 50, download: false},
  {id: 3, time: 1000, download: true},
  {id: 5, time: 50, download: true},
  {id: 5, time: 550, download: false},
  {id: 2, time: 1500, download: true}
]

const result = arr.reduce((res, obj) => {
    const found = res.find(t => t.id === obj.id);

    if(found){
        found.time += obj.time;
        found.download = found.download && obj.download;    
    } else {
        res.push(obj);
    }
    return res;
}, []);

console.log(result);

Comments

0

You can use reduce to:

  • check if the array has the item with the ID on each loop
  • if it does, add the time

else

  • push the object

const arr = [
  {id: 5, time: 100, download: true},
  {id: 2, time: 50, download: false},
  {id: 3, time: 1000, download: true},
  {id: 5, time: 50, download: true},
  {id: 5, time: 550, download: false},
  {id: 2, time: 1500, download: true}
]

console.log(arr.reduce((a, o) => {
  const i = a.findIndex(({id}) => id === o.id)
  i + 1 ? a[i].time += o.time : a.push(o)
  return a
}, []))

Comments

0

One more reduce solution:

const initArr = [
  {id: 5, time: 100, download: true},
  {id: 2, time: 50, download: false},
  {id: 3, time: 1000, download: true},
  {id: 5, time: 50, download: true},
  {id: 5, time: 550, download: false},
  {id: 2, time: 1500, download: true}
]

const merged = initArr.reduce((a,c) => {
    const found = a.find(x => x.id === c.id);
    if (found) {
        found.time += c.time;
        found.download = found.download && c.download;
        return a;
    }
    return [...a, {...c}];
}, []);

console.log(merged);

Comments

0
        function manipulation(initArr){
            var resultArray = [];
            var arrMap = new Map();
            var currIndex = 0;
            initArr.forEach(function(item, index){
                if(arrMap.has(item.id)){
                    var existingItem = resultArray[arrMap.get(item.id)];
                    existingItem.time = item.time+existingItem.time;
                    if(existingItem.download && !item.download){
                        existingItem.download = false;
                    }
                }else{
                    arrMap.set(item.id, currIndex);
                    resultArray.push(item);
                    currIndex++;
                }
            })
            return resultArray;
        }

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.