Write three functions that treat two arrays of items as sets, where two items count as the same when they have the same key. Each takes arrays a and b and a key, and returns a new array.
key is either a function that takes an item and returns its key, or a property name, in which case an item's key is the value of that property. Keys are compared the way a Set compares values, so NaN matches NaN and 1 doesn't match "1".
intersectionBy(a, b, key) returns the items of a whose key is also in b. differenceBy(a, b, key) returns the items of a whose key isn't in b. unionBy(a, b, key) returns the items of a, followed by the items of b whose key isn't in a.
Like a set, a result never holds two items with the same key: keep the first one. Items keep the order they had, and when both arrays have an item with the same key, the result uses the one from a.
key may be slow, so call it at most once per item: a.length + b.length calls in all. None of the functions change a or b.
const selected = [{ id: 1, name: "Ada" }, { id: 3, name: "Cy" }];
const visible = [{ id: 2, name: "Bo" }, { id: 3, name: "Cy" }, { id: 4, name: "Di" }];
intersectionBy(selected, visible, "id"); // [{ id: 3, name: "Cy" }]
differenceBy(selected, visible, "id"); // [{ id: 1, name: "Ada" }]
unionBy(selected, visible, "id");
// [{ id: 1, name: "Ada" }, { id: 3, name: "Cy" }, { id: 2, name: "Bo" }, { id: 4, name: "Di" }]
intersectionBy([1.2, 2.5, 3.1], [3.9, 1.0], Math.floor); // [1.2, 3.1]
const a = [{ id: 1, from: "a" }, { id: 2, from: "a" }, { id: 3, from: "a" }];
const b = [{ id: 3, from: "b" }, { id: 1, from: "b" }];
const result = intersectionBy(a, b, "id");
expect(result).toEqual([{ id: 1, from: "a" }, { id: 3, from: "a" }]);
expect(result[0]).toBe(a[0]);
const a = [{ id: 1, from: "a" }, { id: 2, from: "a" }, { id: 3, from: "a" }];
const b = [{ id: 3, from: "b" }, { id: 1, from: "b" }];
expect(differenceBy(a, b, "id")).toEqual([{ id: 2, from: "a" }]);
expect(differenceBy(a, [], "id")).toEqual(a);
const a = [{ id: 1, from: "a" }, { id: 2, from: "a" }];
const b = [{ id: 2, from: "b" }, { id: 3, from: "b" }];
expect(unionBy(a, b, "id")).toEqual([{ id: 1, from: "a" }, { id: 2, from: "a" }, { id: 3, from: "b" }]);
expect(intersectionBy([1.2, 2.5, 3.1], [3.9, 1.0], Math.floor)).toEqual([1.2, 3.1]);
expect(differenceBy(["a", "B", "c"], ["b"], (s) => s.toLowerCase())).toEqual(["a", "c"]);
expect(unionBy(["a", "B"], ["b", "C"], (s) => s.toLowerCase())).toEqual(["a", "B", "C"]);
expect(unionBy([{ id: 1, n: 1 }, { id: 1, n: 2 }], [{ id: 1, n: 3 }, { id: 2, n: 4 }, { id: 2, n: 5 }], "id")).toEqual([
{ id: 1, n: 1 },
{ id: 2, n: 4 },
]);
expect(intersectionBy([{ id: 1, n: 1 }, { id: 1, n: 2 }], [{ id: 1 }], "id")).toEqual([{ id: 1, n: 1 }]);
expect(differenceBy([{ id: 2, n: 1 }, { id: 2, n: 2 }], [], "id")).toEqual([{ id: 2, n: 1 }]);
expect(intersectionBy([NaN, 1, "2"], [NaN, "1", 2], (x) => x)).toEqual([NaN]);
expect(differenceBy([1, "1"], [1], (x) => x)).toEqual(["1"]);
expect(unionBy([1], ["1"], (x) => x)).toEqual([1, "1"]);
const a = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }];
const b = [{ id: 4 }, { id: 5 }, { id: 2 }];
for (const operation of [intersectionBy, differenceBy, unionBy]) {
const key = spy((item) => item.id);
operation(a, b, key);
expect(key.callCount).toBeLessThanOrEqual(7);
}
const a = [{ id: 1 }, { id: 2 }];
const b = [{ id: 2 }, { id: 3 }];
expect(differenceBy(a, [], "id")).not.toBe(a);
expect(unionBy(a, [], "id")).not.toBe(a);
intersectionBy(a, b, "id");
differenceBy(a, b, "id");
unionBy(a, b, "id");
expect(a).toEqual([{ id: 1 }, { id: 2 }]);
expect(b).toEqual([{ id: 2 }, { id: 3 }]);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Set operations by key. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.