Reputation: 349
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
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
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
Reputation: 371069
For an O(N)
solution, reduce
into an object indexed by user
, whose values are date
s - 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
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