Browse Source

chore: improve list-diff perf

pull/27/head
Antoine Lanoe 7 months ago
parent
commit
3a38a252e8
  1. 24
      src/lib/list-diff/index.ts
  2. 211
      src/lib/list-diff/list-diff.test.ts

24
src/lib/list-diff/index.ts

@ -83,26 +83,26 @@ export const getListDiff = <T>(
return formatSingleListDiff(prevList as T[], LIST_STATUS.DELETED, options); return formatSingleListDiff(prevList as T[], LIST_STATUS.DELETED, options);
} }
const diff: ListDiff["diff"] = []; const diff: ListDiff["diff"] = [];
const prevIndexMatches: number[] = []; const prevIndexMatches = new Set<number>();
nextList.forEach((nextValue, i) => { nextList.forEach((nextValue, i) => {
const prevIndex = prevList.findIndex((prevValue, prevIdx) => { const prevIndex = prevList.findIndex((prevValue, prevIdx) => {
if (prevIndexMatches.has(prevIdx)) {
return false;
}
if (isReferencedObject(prevValue, options.referenceProperty)) { if (isReferencedObject(prevValue, options.referenceProperty)) {
if (isObject(nextValue)) { if (isObject(nextValue)) {
return ( return isEqual(
isEqual( prevValue[options.referenceProperty as string],
prevValue[options.referenceProperty as string], nextValue[options.referenceProperty as string],
nextValue[options.referenceProperty as string],
) && !prevIndexMatches.includes(prevIdx)
); );
} }
return false; return false;
} }
return ( return isEqual(prevValue, nextValue);
isEqual(prevValue, nextValue) && !prevIndexMatches.includes(prevIdx)
);
}); });
if (prevIndex > -1) { if (prevIndex > -1) {
prevIndexMatches.push(prevIndex); prevIndexMatches.add(prevIndex);
} }
const indexDiff = prevIndex === -1 ? null : i - prevIndex; const indexDiff = prevIndex === -1 ? null : i - prevIndex;
if (indexDiff === 0 || options.ignoreArrayOrder) { if (indexDiff === 0 || options.ignoreArrayOrder) {
@ -141,8 +141,8 @@ export const getListDiff = <T>(
}); });
prevList.forEach((prevValue, i) => { prevList.forEach((prevValue, i) => {
if (!prevIndexMatches.includes(i)) { if (!prevIndexMatches.has(i)) {
return diff.splice(i, 0, { return diff.push({
value: prevValue, value: prevValue,
prevIndex: i, prevIndex: i,
newIndex: null, newIndex: null,

211
src/lib/list-diff/list-diff.test.ts

@ -102,20 +102,6 @@ describe("getListDiff", () => {
indexDiff: 0, indexDiff: 0,
status: "equal", status: "equal",
}, },
{
value: "mendes",
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: "verratti",
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: "messi", value: "messi",
prevIndex: null, prevIndex: null,
@ -130,6 +116,20 @@ describe("getListDiff", () => {
indexDiff: -1, indexDiff: -1,
status: "moved", status: "moved",
}, },
{
value: "mendes",
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: "verratti",
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
], ],
}); });
}); });
@ -145,20 +145,6 @@ describe("getListDiff", () => {
indexDiff: 0, indexDiff: 0,
status: "equal", status: "equal",
}, },
{
value: 234,
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 76,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: 200, value: 200,
prevIndex: null, prevIndex: null,
@ -173,6 +159,20 @@ describe("getListDiff", () => {
indexDiff: -1, indexDiff: -1,
status: "moved", status: "moved",
}, },
{
value: 234,
prevIndex: 1,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 76,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
], ],
}); });
}); });
@ -194,13 +194,6 @@ describe("getListDiff", () => {
type: "list", type: "list",
status: "updated", status: "updated",
diff: [ diff: [
{
value: { name: "joe", age: 87 },
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: { name: "paul", age: 32 }, value: { name: "paul", age: 32 },
prevIndex: 2, prevIndex: 2,
@ -222,6 +215,13 @@ describe("getListDiff", () => {
indexDiff: 1, indexDiff: 1,
status: "moved", status: "moved",
}, },
{
value: { name: "joe", age: 87 },
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
], ],
}); });
}); });
@ -335,13 +335,7 @@ describe("getListDiff", () => {
indexDiff: -5, indexDiff: -5,
status: "moved", status: "moved",
}, },
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: true, value: true,
prevIndex: 1, prevIndex: 1,
@ -370,13 +364,6 @@ describe("getListDiff", () => {
indexDiff: 0, indexDiff: 0,
status: "equal", status: "equal",
}, },
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: false, value: false,
prevIndex: null, prevIndex: null,
@ -391,6 +378,20 @@ describe("getListDiff", () => {
indexDiff: null, indexDiff: null,
status: "added", status: "added",
}, },
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
], ],
}); });
}); });
@ -423,20 +424,6 @@ describe("getListDiff", () => {
type: "list", type: "list",
status: "updated", status: "updated",
diff: [ diff: [
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: false, value: false,
prevIndex: null, prevIndex: null,
@ -451,6 +438,20 @@ describe("getListDiff", () => {
indexDiff: null, indexDiff: null,
status: "added", status: "added",
}, },
{
value: true,
prevIndex: 2,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: 13,
prevIndex: 7,
newIndex: null,
indexDiff: null,
status: "deleted",
},
], ],
}); });
}); });
@ -536,13 +537,6 @@ describe("getListDiff", () => {
type: "list", type: "list",
status: "updated", status: "updated",
diff: [ diff: [
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: { id: 8, age: 77 }, value: { id: 8, age: 77 },
prevIndex: 4, prevIndex: 4,
@ -572,18 +566,25 @@ describe("getListDiff", () => {
status: "equal", status: "equal",
}, },
{ {
value: { id: 55, character: { strength: 66 } }, value: { id: 99, character: { strength: 69 } },
prevIndex: 5, prevIndex: null,
newIndex: 4,
indexDiff: null,
status: "added",
},
{
value: "hello",
prevIndex: 0,
newIndex: null, newIndex: null,
indexDiff: null, indexDiff: null,
status: "deleted", status: "deleted",
}, },
{ {
value: { id: 99, character: { strength: 69 } }, value: { id: 55, character: { strength: 66 } },
prevIndex: null, prevIndex: 5,
newIndex: 4, newIndex: null,
indexDiff: null, indexDiff: null,
status: "added", status: "deleted",
}, },
], ],
}); });
@ -646,13 +647,6 @@ describe("getListDiff", () => {
type: "list", type: "list",
status: "updated", status: "updated",
diff: [ diff: [
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{ {
value: { id: 8, age: 77 }, value: { id: 8, age: 77 },
prevIndex: 4, prevIndex: 4,
@ -682,18 +676,25 @@ describe("getListDiff", () => {
status: "equal", status: "equal",
}, },
{ {
value: { id: 55, character: { strength: 66 } }, value: { id: 99, character: { strength: 69 } },
prevIndex: 5, prevIndex: null,
newIndex: 4,
indexDiff: null,
status: "added",
},
{
value: "hello",
prevIndex: 0,
newIndex: null, newIndex: null,
indexDiff: null, indexDiff: null,
status: "deleted", status: "deleted",
}, },
{ {
value: { id: 99, character: { strength: 69 } }, value: { id: 55, character: { strength: 66 } },
prevIndex: null, prevIndex: 5,
newIndex: 4, newIndex: null,
indexDiff: null, indexDiff: null,
status: "added", status: "deleted",
}, },
], ],
}); });
@ -790,3 +791,35 @@ describe("getListDiff", () => {
}); });
}); });
}); });
describe("Performance", () => {
it("should correctly stream diff for 10.000 entries", () => {
const generateLargeList = (size: number, idPrefix: string) => {
return Array.from({ length: size }, (_, i) => ({
id: `${idPrefix}-${i}`,
value: i,
}));
};
const prevList = generateLargeList(10_000, "prev");
const nextList = [
...generateLargeList(5000, "prev"),
...generateLargeList(5000, "next"),
];
const receivedChunks = getListDiff(prevList, nextList).diff;
const deletions = receivedChunks.filter(
(diff) => diff.status === LIST_STATUS.DELETED,
);
const additions = receivedChunks.filter(
(diff) => diff.status === LIST_STATUS.ADDED,
);
const updates = receivedChunks.filter(
(diff) => diff.status === LIST_STATUS.EQUAL,
);
expect(receivedChunks.length).toBe(15_000); // 5000 deletions + 5000 equal + 5000 additions
expect(deletions.length).toBe(5000);
expect(additions.length).toBe(5000);
expect(updates.length).toBe(5000);
});
});

Loading…
Cancel
Save