List Set Operations — Python 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 Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""list-set-ops — union / intersection / difference / symmetric difference of
two newline-separated lists.
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the List Set Operations tool,
ported from src/lib/list-set-ops.ts (the canonical TypeScript
implementation) and cli/list-set-ops/list-set-ops.go (the Go twin).
License: display source — part of CosmoDev's polyglot tool pages.
Design goals:
- Pure + deterministic; never raises.
- Functionally equivalent to the TS/Go reference: same inputs -> same outputs.
- Self-contained: stdlib only (no pip packages).
Algorithm: each list is parsed (optionally trimmed + deduped by first-seen
casing), then compared by exact string equality unless ``caseInsensitive``
folds the comparison key (the original first-seen casing is still emitted).
Results are joined with "\\n".
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Literal, Optional
__all__ = ["SetOp", "SetOptions", "set_ops", "parse_list"]
SetOp = Literal["union", "intersect", "difference", "symmetricDifference"]
@dataclass
class SetOptions:
"""Options mirror the TS ``SetOptions`` / Go ``Options`` struct.
Every field defaults to the TS default.
"""
caseInsensitive: bool = False
"""Fold case for the membership comparison (the original first-seen casing
is still emitted)."""
trim: Optional[bool] = None
"""Trim items and drop empty lines. ``None`` -> True (the default); an
explicit ``False`` disables trimming — exactly like the TS lib's
distinction between omitted (-> trim) and ``trim: False``."""
def _trim_enabled(options: SetOptions) -> bool:
"""``None`` (default) means trim ON; a concrete bool is used as-is. Mirrors
the TS ``opts.trim !== false`` and Go ``opts.Trim == nil || *opts.Trim``
rules."""
return True if options.trim is None else options.trim
def parse_list(input: str, options: SetOptions) -> List[str]:
"""Split a newline list into a deduped list of items.
Empty input yields no items (not a single empty item). When trimming is
enabled (default) items are whitespace-trimmed and empty lines dropped.
Duplicates within the list are removed keeping the first-seen original
casing.
"""
if input == "":
return [] # empty input = no items
trim = _trim_enabled(options)
ci = options.caseInsensitive
items = input.split("\n")
if trim:
# str.strip is the Unicode-aware whitespace trim (matches JS String.trim).
items = [s for s in (x.strip() for x in items) if s != ""]
seen: set[str] = set()
out: List[str] = []
for item in items:
key = item.lower() if ci else item
if key in seen:
continue
seen.add(key)
out.append(item)
return out
def set_ops(a: str, b: str, op: SetOp, options: Optional[SetOptions] = None) -> str:
"""Compute a set operation over two newline-separated lists. Never raises.
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. The result is 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)
An unknown op yields the empty string (no default branch).
"""
if options is None:
options = SetOptions()
ci = options.caseInsensitive
key_of = (lambda s: s.lower()) if ci else (lambda s: s)
list_a = parse_list(a, options)
list_b = parse_list(b, options)
keys_a = {key_of(i) for i in list_a}
keys_b = {key_of(i) for i in list_b}
out: List[str] = []
if op == "union":
seen: set[str] = set()
for item in list_a:
seen.add(key_of(item))
out.append(item) # list_a is already deduped by parse_list
for item in list_b:
k = key_of(item)
if k in seen:
continue
seen.add(k)
out.append(item)
elif op == "intersect":
for item in list_a:
if key_of(item) in keys_b:
out.append(item)
elif op == "difference":
for item in list_a:
if key_of(item) not in keys_b:
out.append(item)
elif op == "symmetricDifference":
for item in list_a:
if key_of(item) not in keys_b:
out.append(item)
for item in list_b:
if key_of(item) not in keys_a:
out.append(item)
# unknown op -> out stays empty (no default branch)
return "\n".join(out)
# ---------- showcase (the canonical suite lives in src/lib) ----------
if __name__ == "__main__":
A = "apple\nbanana\ncherry"
B = "banana\ncherry\ndate"
ci = SetOptions(caseInsensitive=True)
assert set_ops(A, B, "union") == "apple\nbanana\ncherry\ndate"
assert set_ops(A, B, "intersect") == "banana\ncherry"
assert set_ops(A, B, "difference") == "apple"
assert set_ops(A, B, "symmetricDifference") == "apple\ndate"
# case-folding compares keys, but the first-seen original casing is emitted.
assert set_ops("HELLO", "hello", "intersect", ci) == "HELLO"
assert set_ops("X\nApple", "x\nAPPLE\nmango", "union", ci) == "X\nApple\nmango"
print("ok")
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 →