Home > Software engineering >  How to filter an array of objects based on multiple properties in JavaScript/Typescript?
How to filter an array of objects based on multiple properties in JavaScript/Typescript?

Time:07-23

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:

  • Most recent version
  • Valid ones { invalid: false }

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!

CodePudding user response:

You can do it using Array.prototype.reduce.

Update the object corresponding to a particular name only if:

  • it's valid
  • it's new
  • it has a higher version that the currently stored object

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);

CodePudding user response:

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.

CodePudding user response:

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;
});
  • Related