Reputation: 13
What is the best way to filter an array of objects based on multiple property conditions and preserve the original order. So what has to happen is to filter the array based on these two conditions:
In other words leave only the valid ones and ones with highest version number(version number could be any).
const arr = [
{ name: 'John', invalid: false, version: 1 },
{ name: 'John', invalid: false, version: 2 },
{ name: 'John', invalid: true, version: 1 },
{ name: 'John', invalid: true, version: 5 },
{ name: 'John', invalid: true, version: 2 },
{ name: 'Samuel', invalid: false, version: 1 },
{ name: 'Samuel', invalid: false, version: 2 },
{ name: 'Samuel', invalid: true, version: 1 },
];
to...
const arr = [
{ name: 'John', invalid: false, version: 2 },
{ name: 'Samuel', invalid: false, version: 2 }
];
Thanks in advance!
Upvotes: 1
Views: 1674
Reputation: 8168
You can do it using Array.prototype.reduce.
Update the object corresponding to a particular name
only if:
const data = [
{ name: "John", invalid: false, version: 1 },
{ name: "John", invalid: false, version: 2 },
{ name: "John", invalid: true, version: 1 },
{ name: "John", invalid: true, version: 5 },
{ name: "John", invalid: true, version: 2 },
{ name: "Samuel", invalid: false, version: 1 },
{ name: "Samuel", invalid: false, version: 2 },
{ name: "Samuel", invalid: true, version: 1 },
];
const result = Object.values(
data.reduce((acc, d) => {
if (d.invalid) return acc;
if (!acc[d.name] || d.version > acc[d.name].version) {
acc[d.name] = d;
}
return acc;
}, {})
);
console.log(result);
In TS, you can create separate types for valid and invalid data using the union type.
type ValidData = { name: string; invalid: false; version: number };
type InvalidData = { name: string; invalid: true; version: number };
const data: (ValidData | InvalidData)[] = [
{ name: "John", invalid: false, version: 1 },
{ name: "John", invalid: false, version: 2 },
{ name: "John", invalid: true, version: 1 },
{ name: "John", invalid: true, version: 5 },
{ name: "John", invalid: true, version: 2 },
{ name: "Samuel", invalid: false, version: 1 },
{ name: "Samuel", invalid: false, version: 2 },
{ name: "Samuel", invalid: true, version: 1 },
];
const result = Object.values(
data.reduce((acc: { [name: string]: ValidData }, d) => {
if (d.invalid) return acc;
if (!acc[d.name] || d.version > acc[d.name].version) {
acc[d.name] = d;
}
return acc;
}, {})
);
console.log(result);
Upvotes: 1
Reputation: 128
You could just use a lookup table, this is in O(n) runtime.
Basically iterate once through the array, remember for each valid name the highest version you saw, and then use that table as a filter for the original array.
const lookup = {} as Record<string, {name: string, version: number, invalid: boolean}>;
arr.forEach((it) => {
const stored = lookup[it.name];
if (stored) {
if (!it.invalid && it.version > stored.version) {
lookup[it.name] = it;
}
} else {
lookup[it.name] = it;
}
})
const filtered = arr.filter((it) => {
const stored = lookup[it.name];
const result = !it.invalid && it.version === stored.version;
if (result) { //if no duplicates wanted
delete lookup[it.name];
}
return result;
});
Upvotes: 0
Reputation: 51
I'd recommend doing this in several stages. Assuming that the order of the array isn't important, you can .sort
it so the most recent version is first, and keep track of which you have added, and also filter out invalid ones. Something like this:
function filterThing(thing) {
const added = new Set();
return thing.sort((a, b) => b.version - a.version).filter((item) => {
if (thing.invalid || added.has(thing.name)) {
return false;
}
added.add(thing.name);
return true;
}
}
If you need to have the items in another order, you could sort after that. If you need to preserve the original order of the items, that's a more complicated algorithm.
Upvotes: 0