4

I have an array like this:

const array=[ {id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4} ]

My GOAL is to be like this:

const array=[ {id:1, quantity:2}, {id:0, quantity:4} ]

The order of the object does not matter as long as it can find the 'id' with the larger quantity

I tried filter + findIndex, map +filter, etc but I kept making mistake. I need help.

0

6 Answers 6

5

You could use a hash table and check if an object with the same id is in the result set. If the actual quantity is greater, assign the actual object.

var array = [{ id: 0, quantity: 1 }, { id: 1, quantity: 2 }, { id: 0, quantity: 4 }],
    hash = Object.create(null),
    unique = array.reduce(function (r, o) {
        if (!(o.id in hash)) {
            hash[o.id] = r.push(o) - 1;
            return r;
        }
        if (o.quantity > r[hash[o.id]].quantity) {
            r[hash[o.id]] = o;
        }
        return r;
    }, []);
    
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

2 Comments

I'm shocked that you didn't arrange for the hash to be implicitly declared as part of a closure around the reduce function.
@torazaburo, it's easier to understand.
2

Let's get all the ids first. Then for each unique id, we'll find all the relevant objects in the array (using filter) and get the maximum of all their quantity properties.

function uniqByMax(arr) {
  const ids = arr.map(elt => elt.id);
  const uniqueIds = uniq(ids);

  return uniqueIds.map(id => {
    const matchingElts = arr.filter(elt => elt.id === id);
    const quantities = matchingElts.map(elt => elt.quantity);
    const quantity = Math.max(...quantities);

    return {id, quantity};
  });
}

You can grab uniq off the net somewhere, or use a library, or write it yourself.

Here is another approach, which uses a filter with side effects, if that is your cup of tea:

function uniqByMax(arr) {
  return arr.filter(elt => {
    const previous = arr.find(e2 => e2.id === elt.id);
    if (previous === elt) return true;
    previous.quantity = Math.max(previous.quantity, elt.quantity);
  });
}

The basic idea is to loop through the elements. For each element, we find if there is an earlier element with the same id. If not, retain this element (return true;); otherwise, update the quantity of the earlier element we retained with the maximum of its quantity and this element's quantity.

In the interest of generality, it could be interesting to parameterize this function by the property we are finding unique values of, and the way to update/combine multiple items:

function uniqTransform(arr, prop, combine) {
  return arr.filter(elt => {
    const previous = arr.find(e2 => e2[prop] === elt[prop]);
    if (previous === elt) return true;
    combine(previous, elt);
  });

Then we call this with

uniqTransform(arr, 'id', (a, b) => a.quantity = Math.max(a.quantity, b.quantity));

Or we could generalize it further by using another function to identify elements which are supposed to be considered the same for uniqueifying purposes, which we will call uniqueFunc:

function uniqTransform(arr, uniqueFunc, combineFunc) {
  return arr.filter(elt => {
    const previous = arr.find(e2 => uniqueFunc(elt, e2));
    if (previous === elt) return true;
    combineFunc(previous, elt);
  });

Then we call this with

uniqTransform(
  arr, 
  (a, b) => a.id === b.id, 
  (a, b) => a.quantity = Math.max(a.quantity, b.quantity));

1 Comment

Thanks for the detailed explanation!
1

Here you go.

const array=[ {id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4} ];

const arrayFiltered = [];

array.forEach(obj => {
    const item = arrayFiltered.find(thisItem => thisItem.id === obj.id);
    if (item) {
        if (item.quantity < obj.quantity) {
            item.quantity = obj.quantity;
        }
        
        return;
    }
    
    arrayFiltered.push(obj);
});

console.log(arrayFiltered);

2 Comments

@Amav Dude, i tried your solution and it works but man i kinda confused how it works. Can i add your slack or telegram or anything? i need to ask you
We're creating an empty array, arrayFiltered. In the forEach, we're looping through array. For each item in array, we look in arrayFiltered to see if we can find an item with the same id. If it exists, we take that object and make the quantity property on the object equal to the higher of the two. If it doesn't exist in arrayFiltered, we push it in. Hope this helps.
1

Can you work with objects? There's a simple alternative using this code

const array = [{id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4}]
const object = {}
array.forEach((element) => {
  object[element.id] = element
})

The only problem is that you will override the previous element each time you found an element with the same id.

1 Comment

I use the answer that @Arnav gave me
0

This is similar to @Arnav solution, but cleaned a bit. The idea is to iterate over array preparing resulting array res and test each element. If item is not found by id in resulting array - we add it. If item with the same id already in resulting array we then check if it has greater quantity, if so - we replace the whole item in resulting array, that suits for complex items with many properties.

const res = [];
arr.forEach(it => {
  const i = res.findIndex(v => v.id === it.id)
  if (i === -1) res.push(it)
  else if (res[i].quantity < it.quantity) res[i] = it
})

Comments

0

Use the id values as keys to a new object; only replace a given existing key in the object, if the value of quantity is greater than that in the object. Use Object.values on the object to get the final result.

const
      input =[ {id:0, quantity:1}, {id:1, quantity:2}, {id:0, quantity:4} ],
      
      output = Object.values(
          input.reduce(
              (acc, {id,quantity}) =>
                acc.id && acc.id.id === id && quantity < acc.id.quantity ?
                  acc : ({...acc,[id]:{id,quantity}}), {}
          )
      );
      
console.log( output );

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.