YvonC
YvonC

Reputation: 349

JS - Filter an array of objects for duplicates of one property, and decide which object to keep based on another property

Trying to filter an array of objects by eliminating objects that have a specific property that exists already in another object (duplicate). The decision which object to remove should be based on another property.

Example: For an array of objects, that might look like this, the goal would be to filter all "user" duplicates and keep the one with the oldest "date" property.

const arr = [
    {user: 'Alex', date: '1540801929945'},
    {user: 'Bill', date: '1640801929946'},
    {user: 'Carl', date: '1740801929947'},
    {user: 'Alex', date: '1840801929948'},
]

Expected outcome:

filteredArr = [
    {user: 'Alex', date: '1540801929945'},
    {user: 'Bill', date: '1640801929946'},
    {user: 'Carl', date: '1740801929947'},
]

I managed the basic filtering that works for keeping only unique objects.

const filteredArr = arr.reduce((unique, o) => {
    if(!unique.some(obj => obj.user === o.user) {
      unique.push(o);
    }
    return unique;
},[]);

Though I can't figure out, what to do so that in the case of a duplicate the "oldest" object stays and the latest get removed. Thanks so much for your help! Really appreciated.

Upvotes: 5

Views: 5144

Answers (4)

Nina Scholz
Nina Scholz

Reputation: 386756

A solution with Map and a single loop approach

var array = [{ user: 'Alex', date: '1540801929945' }, { user: 'Bill', date: '1640801929946' }, { user: 'Carl', date: '1740801929947' }, { user: 'Alex', date: '1840801929948' }],
    filtered = Array.from(array
        .reduce((m, o) => !m.has(o.user) || m.get(o.user).data > o.date
            ? m.set(o.user, o)
            : m, new Map)
       .values());
      
console.log(filtered);
.as-console-wrapper { max-height: 100% !important; top: 0; }

For sorted data (take sorting in advance by date), you could use a Set for filtering.

var array = [{ user: 'Alex', date: '1540801929945' }, { user: 'Bill', date: '1640801929946' }, { user: 'Carl', date: '1740801929947' }, { user: 'Alex', date: '1840801929948' }],
    filtered = array
        .sort(({ date: a }, { date: b }) => a - b)
        .filter((s => ({ user }) => !s.has(user) && s.add(user))(new Set));
      
console.log(filtered);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

Lazar Drobnjak
Lazar Drobnjak

Reputation: 1

I believe you actually need to know the index of the non-unique element (Object with name "Alex" in your case) and then compare their dates, saving the older one.

Array.prototype.find wil lnot do you mouch good in this case. However, Array.prototype.find can be of help since it takes a callback which returns a boolean( just like 'some') BUT it returns the index of the element matched by your callback, or undefined if no such element is found.

Give it a try, it cant hurt :)

Upvotes: -1

CertainPerformance
CertainPerformance

Reputation: 371069

For an O(N) solution, reduce into an object indexed by user, whose values are dates - on each iteration, if something at that user already exists, keep only the lowest date. Then, iterate over the object's entries to turn it back into an array:

const arr = [
    {user: 'Alex', date: '1540801929945'},
    {user: 'Bill', date: '1640801929946'},
    {user: 'Carl', date: '1740801929947'},
    {user: 'Alex', date: '1840801929948'},
    {user: 'Carl', date: '1340801929947'},
];

const arrByUser = arr.reduce((a, { user, date }) => {
  if (!a[user]) a[user] = date;
  else if (a[user].localeCompare(date) === 1) a[user] = date;
  return a;
}, {});
const output = Object.entries(arrByUser)
  .map(([user, date]) => ({ user, date }));
console.log(output);

Upvotes: 1

Ori Drori
Ori Drori

Reputation: 192592

Reduce the array to an object with the user as the key. If a user is not in unique object, or if it's date is "older" than the what that is, add it to the object. Convert the object to array with Object.values().

Note: since you the dates are string, convert them to a number while comparing (I use the + operator).

const array = [{ user: 'Alex', date: '1540801929945' }, { user: 'Bill', date: '1640801929946' }, { user: 'Carl', date: '1740801929947' }, { user: 'Alex', date: '1840801929948' }];


const filteredArray = Object.values(array.reduce((unique, o) => {
  if(!unique[o.user] || +o.date > +unique[o.user].date) unique[o.user] = o;
  
  return unique;
}, {}));

console.log(filteredArray);

Upvotes: 7

Related Questions