From 4e1464b1e179a72ed7ef657f1cbbe2cd09645b1c Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Tue, 24 Mar 2026 09:24:29 +0100 Subject: [PATCH] fix Set Difference and Set Intersection preserve duplicates from first sample Deduplicate results in both operations to match mathematical set semantics, consistent with Set Union which already deduplicates. Closes #2241 --- src/core/operations/SetDifference.mjs | 9 +++++++- src/core/operations/SetIntersection.mjs | 9 +++++++- tests/operations/tests/SetDifference.mjs | 22 ++++++++++++++++++++ tests/operations/tests/SetIntersection.mjs | 24 +++++++++++++++++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/core/operations/SetDifference.mjs b/src/core/operations/SetDifference.mjs index dc46c079d4..d5ab92d368 100644 --- a/src/core/operations/SetDifference.mjs +++ b/src/core/operations/SetDifference.mjs @@ -75,9 +75,16 @@ class SetDifference extends Operation { * @returns {Object[]} */ runSetDifference(a, b) { + const excluded = new Set(b); + const seen = new Set(); + return a .filter((item) => { - return b.indexOf(item) === -1; + if (excluded.has(item) || seen.has(item)) { + return false; + } + seen.add(item); + return true; }) .join(this.itemDelimiter); } diff --git a/src/core/operations/SetIntersection.mjs b/src/core/operations/SetIntersection.mjs index 7e6dbe1088..423fcd4f7a 100644 --- a/src/core/operations/SetIntersection.mjs +++ b/src/core/operations/SetIntersection.mjs @@ -75,9 +75,16 @@ class SetIntersection extends Operation { * @returns {Object[]} */ runIntersect(a, b) { + const included = new Set(b); + const seen = new Set(); + return a .filter((item) => { - return b.indexOf(item) > -1; + if (!included.has(item) || seen.has(item)) { + return false; + } + seen.add(item); + return true; }) .join(this.itemDelimiter); } diff --git a/tests/operations/tests/SetDifference.mjs b/tests/operations/tests/SetDifference.mjs index 40fac52449..5836cb36c7 100644 --- a/tests/operations/tests/SetDifference.mjs +++ b/tests/operations/tests/SetDifference.mjs @@ -53,4 +53,26 @@ TestRegister.addTests([ }, ], }, + { + name: "Set Difference: duplicates in first set are removed", + input: "red,red,blue\n\nblue", + expectedOutput: "red", + recipeConfig: [ + { + op: "Set Difference", + args: ["\n\n", ","], + }, + ], + }, + { + name: "Set Difference: duplicates in both sets", + input: "1 1 2 2 3\n\n2 2 3 3", + expectedOutput: "1", + recipeConfig: [ + { + op: "Set Difference", + args: ["\n\n", " "], + }, + ], + }, ]); diff --git a/tests/operations/tests/SetIntersection.mjs b/tests/operations/tests/SetIntersection.mjs index c9146c01d5..a638db4ab5 100644 --- a/tests/operations/tests/SetIntersection.mjs +++ b/tests/operations/tests/SetIntersection.mjs @@ -52,5 +52,27 @@ TestRegister.addTests([ args: ["z", "-"], }, ], - } + }, + { + name: "Set Intersection: duplicates in first set are removed", + input: "red,red,blue\n\nred,blue", + expectedOutput: "red,blue", + recipeConfig: [ + { + op: "Set Intersection", + args: ["\n\n", ","], + }, + ], + }, + { + name: "Set Intersection: duplicates in both sets", + input: "1 1 2 2 3\n\n2 2 3 3 4", + expectedOutput: "2 3", + recipeConfig: [ + { + op: "Set Intersection", + args: ["\n\n", " "], + }, + ], + }, ]);