List Set Operations — Go 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 Go implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Package listsetops is the Go twin of CosmoDev's src/lib/list-set-ops.ts
// (dual source: the web lib is TypeScript, the CLI lib is Go — kept in
// lock-step). It computes union / intersection / difference / symmetric
// difference of two newline-separated lists. Pure + deterministic, never
// panics. The table-driven tests in list-set-ops_test.go share vectors with
// src/lib/list-set-ops.test.ts so the two implementations are held to the
// same contract.
//
// Algorithm mirrors the TS lib exactly: 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".
package listsetops
import "strings"
// Op is the set operation to compute over two lists. It matches the TS
// SetOp union literal-for-literal.
type Op string
const (
OpUnion Op = "union"
OpIntersect Op = "intersect"
OpDifference Op = "difference"
OpSymmetricDifference Op = "symmetricDifference"
)
// Options configures SetOps. The zero value (Options{}) matches the TS
// default (setOps(a, b, op) with no options): trim enabled,
// case-sensitive comparison.
//
// Trim is a *bool so the zero value means "trim enabled (default)" while a
// non-nil false still disables trimming — exactly like the TS lib's
// distinction between omitted (→ trim) and trim: false.
type Options struct {
CaseInsensitive bool // default false
Trim *bool // nil → true (default); non-nil used verbatim
}
// parseList splits a newline list into a deduped slice of items. It mirrors
// parseList() in the TS lib: empty input yields no items (not a single empty
// item); when trim is enabled (default) items are whitespace-trimmed and
// empty lines are dropped; duplicates within the list are removed keeping
// the first-seen original casing.
func parseList(input string, opts Options) []string {
if input == "" {
return nil // empty input = no items
}
trim := opts.Trim == nil || *opts.Trim
ci := opts.CaseInsensitive
items := strings.Split(input, "\n")
if trim {
filtered := items[:0]
for _, s := range items {
s = strings.TrimSpace(s)
if s != "" {
filtered = append(filtered, s)
}
}
items = filtered
}
seen := make(map[string]struct{}, len(items))
out := make([]string, 0, len(items))
for _, item := range items {
key := item
if ci {
key = strings.ToLower(item)
}
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
out = append(out, item)
}
return out
}
// SetOps computes a set operation over two newline-separated lists. It is
// the Go twin of setOps() in src/lib/list-set-ops.ts and must agree with it
// on every shared vector. Items are compared by exact string equality unless
// Options.CaseInsensitive folds the key (first-seen original casing 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, matching the TS switch with no
// default case.
func SetOps(a, b string, op Op, opts Options) string {
ci := opts.CaseInsensitive
keyOf := func(s string) string {
if ci {
return strings.ToLower(s)
}
return s
}
listA := parseList(a, opts)
listB := parseList(b, opts)
keysA := make(map[string]struct{}, len(listA))
for _, item := range listA {
keysA[keyOf(item)] = struct{}{}
}
keysB := make(map[string]struct{}, len(listB))
for _, item := range listB {
keysB[keyOf(item)] = struct{}{}
}
var out []string
switch op {
case OpUnion:
seen := make(map[string]struct{})
for _, item := range listA {
seen[keyOf(item)] = struct{}{}
out = append(out, item) // listA is already deduped by parseList
}
for _, item := range listB {
k := keyOf(item)
if _, ok := seen[k]; ok {
continue
}
seen[k] = struct{}{}
out = append(out, item)
}
case OpIntersect:
for _, item := range listA {
if _, ok := keysB[keyOf(item)]; ok {
out = append(out, item)
}
}
case OpDifference:
for _, item := range listA {
if _, ok := keysB[keyOf(item)]; !ok {
out = append(out, item)
}
}
case OpSymmetricDifference:
for _, item := range listA {
if _, ok := keysB[keyOf(item)]; !ok {
out = append(out, item)
}
}
for _, item := range listB {
if _, ok := keysA[keyOf(item)]; !ok {
out = append(out, item)
}
}
}
return strings.Join(out, "\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 →