cyberchef/src/core/operations/SetDifference.mjs
Willi Ballenthin 4e1464b1e1 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
2026-03-24 09:24:49 +01:00

95 lines
2.3 KiB
JavaScript

/**
* @author d98762625 [d98762625@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Set Difference operation
*/
class SetDifference extends Operation {
/**
* Set Difference constructor
*/
constructor() {
super();
this.name = "Set Difference";
this.module = "Default";
this.description = "Calculates the difference, or relative complement, of two sets.";
this.infoURL = "https://wikipedia.org/wiki/Complement_(set_theory)#Relative_complement";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Sample delimiter",
type: "binaryString",
value: "\\n\\n"
},
{
name: "Item delimiter",
type: "binaryString",
value: ","
},
];
}
/**
* Validate input length
*
* @param {Object[]} sets
* @throws {Error} if not two sets
*/
validateSampleNumbers(sets) {
if (!sets || (sets.length !== 2)) {
throw new OperationError("Incorrect number of sets, perhaps you need to modify the sample delimiter or add more samples?");
}
}
/**
* Run the difference operation
*
* @param {string} input
* @param {Object[]} args
* @returns {string}
* @throws {OperationError}
*/
run(input, args) {
[this.sampleDelim, this.itemDelimiter] = args;
const sets = input.split(this.sampleDelim);
this.validateSampleNumbers(sets);
return this.runSetDifference(...sets.map(s => s.split(this.itemDelimiter)));
}
/**
* Get elements in set a that are not in set b
*
* @param {Object[]} a
* @param {Object[]} b
* @returns {Object[]}
*/
runSetDifference(a, b) {
const excluded = new Set(b);
const seen = new Set();
return a
.filter((item) => {
if (excluded.has(item) || seen.has(item)) {
return false;
}
seen.add(item);
return true;
})
.join(this.itemDelimiter);
}
}
export default SetDifference;