Skip to content

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 →