List Set Operations — TypeScript source
Compute the union, intersection, difference, or symmetric difference of two newline-separated lists. Deduped results with optional trim and case-insensitive matching. Runs entirely in your browser, with a shareable link.
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure list set operations - compute union / intersection / difference /
// symmetric difference of two newline-separated lists. Zero deps - the
// unit-test surface for the List Set Operations tool.
export type SetOp = 'union' | 'intersect' | 'difference' | 'symmetricDifference';
export interface SetOptions {
caseInsensitive?: boolean;
trim?: boolean;
}
/**
* Split a newline list into a deduped array of items.
* - Trims whitespace and drops empty lines when `trim` is true (default).
* - Deduplicates within the list using the first-seen original casing.
*/
function parseList(input: string, opts: SetOptions): string[] {
if (input === '') return []; // empty input = no items (not a single empty item)
const trim = opts.trim !== false; // default true
const ci = !!opts.caseInsensitive;
let items = input.split('\n');
if (trim) {
items = items.map((s) => s.trim()).filter((s) => s !== '');
}
const seen = new Set<string>();
const out: string[] = [];
for (const item of items) {
const key = ci ? item.toLowerCase() : item;
if (seen.has(key)) continue;
seen.add(key);
out.push(item);
}
return out;
}
/**
* Compute a set operation over two newline-separated lists. Items are compared
* by exact string equality unless `caseInsensitive` is set, in which case the
* comparison is case-folded but the first-seen original casing is emitted.
* Results are joined with `\n`.
*
* - union → every unique item, a then b (first-seen casing)
* - intersect → items present in both lists (a's order/casing)
* - difference → items in a not present in b
* - symmetricDifference→ items in exactly one list (a-only then b-only)
*/
export function setOps(a: string, b: string, op: SetOp, opts: SetOptions = {}): string {
const ci = !!opts.caseInsensitive;
const keyOf = (s: string): string => (ci ? s.toLowerCase() : s);
const listA = parseList(a, opts);
const listB = parseList(b, opts);
const keysA = new Set(listA.map(keyOf));
const keysB = new Set(listB.map(keyOf));
const out: string[] = [];
switch (op) {
case 'union': {
const seen = new Set<string>();
for (const item of listA) {
seen.add(keyOf(item));
out.push(item); // listA is already deduped by parseList
}
for (const item of listB) {
const k = keyOf(item);
if (seen.has(k)) continue;
seen.add(k);
out.push(item);
}
break;
}
case 'intersect': {
for (const item of listA) {
if (keysB.has(keyOf(item))) out.push(item);
}
break;
}
case 'difference': {
for (const item of listA) {
if (!keysB.has(keyOf(item))) out.push(item);
}
break;
}
case 'symmetricDifference': {
for (const item of listA) {
if (!keysB.has(keyOf(item))) out.push(item);
}
for (const item of listB) {
if (!keysA.has(keyOf(item))) out.push(item);
}
break;
}
}
return out.join('\n');
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →