List Set Operations — Zig 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 Zig 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. Zig port (canonical TS:
//! src/lib/list-set-ops.ts; Go twin: cli/list-set-ops). No stdlib set type is
//! stable across Zig versions, so — like the C port — membership is a linear
//! scan over the already-deduped item slices. ASCII-only trim/fold.
const std = @import("std");
const SetOp = enum { union_, intersect, difference, symmetric_difference };
const Options = struct { case_insensitive: bool = false, trim: bool = true };
// Membership-key compare: byte-exact, or ASCII case-folded when ci is set.
fn keyEq(a: []const u8, b: []const u8, ci: bool) bool {
if (a.len != b.len) return false;
for (a, b) |x, y| {
if ((if (ci) std.ascii.toLower(x) else x) != (if (ci) std.ascii.toLower(y) else y)) return false;
}
return true;
}
// Is item's key in list? (list is deduped, so it IS the key set.)
fn hasKey(list: []const []const u8, item: []const u8, ci: bool) bool {
for (list) |s| if (keyEq(s, item, ci)) return true;
return false;
}
// Split a newline list into a deduped slice (first-seen casing kept); trim
// drops ASCII-whitespace edges and empty lines (the TS default).
fn parseList(alloc: std.mem.Allocator, input: []const u8, o: Options) []const []const u8 {
if (input.len == 0) return &.{}; // empty input = no items
const items = alloc.alloc([]const u8, std.mem.count(u8, input, "\n") + 1) catch return &.{};
var n: usize = 0;
var it = std.mem.splitScalar(u8, input, '\n');
while (it.next()) |raw| {
const item: []const u8 = if (o.trim) std.mem.trim(u8, raw, " \t\r") else raw;
if ((o.trim and item.len == 0) or hasKey(items[0..n], item, o.case_insensitive)) continue;
items[n] = item;
n += 1;
}
return items[0..n];
}
// Compute a set op over two lists; returns a "\n"-joined slice.
fn setOps(alloc: std.mem.Allocator, a: []const u8, b: []const u8, op: SetOp, o: Options) []const u8 {
const la = parseList(alloc, a, o);
const lb = parseList(alloc, b, o);
const ci = o.case_insensitive;
const out = alloc.alloc(u8, a.len + b.len + 4) catch return ""; // items + separators fit
var w: usize = 0;
const emit = struct { // write "\n"+item so the newline lands only BETWEEN items
fn f(buf: []u8, p: *usize, s: []const u8) void {
buf[p.*] = '\n';
p.* += 1;
@memcpy(buf[p.* .. p.* + s.len], s);
p.* += s.len;
}
}.f;
switch (op) {
.union_ => { // a (already deduped) then the b items not in a
for (la) |s| emit(out, &w, s);
for (lb) |s| if (!hasKey(la, s, ci)) emit(out, &w, s);
},
.intersect => { for (la) |s| if (hasKey(lb, s, ci)) emit(out, &w, s); }, // in both, a's order/casing
.difference => { for (la) |s| if (!hasKey(lb, s, ci)) emit(out, &w, s); }, // in a, not in b
.symmetric_difference => { // exactly one list, a-only then b-only
for (la) |s| if (!hasKey(lb, s, ci)) emit(out, &w, s);
for (lb) |s| if (!hasKey(la, s, ci)) emit(out, &w, s);
},
} // unknown op -> "" (w stays 0)
return if (w == 0) "" else out[1..w];
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
const A = "apple\nbanana\ncherry", B = "banana\ncherry\ndate";
std.debug.print("{s}\n", .{setOps(alloc, A, B, .union_, .{})}); // apple\nbanana\ncherry\ndate
std.debug.print("{s}\n", .{setOps(alloc, A, B, .intersect, .{})}); // banana\ncherry
std.debug.print("{s}\n", .{setOps(alloc, A, B, .difference, .{})}); // apple
std.debug.print("{s}\n", .{setOps(alloc, A, B, .symmetric_difference, .{})}); // apple\ndate
std.debug.print("{s}\n", .{setOps(alloc, "HELLO", "hello", .intersect, .{ .case_insensitive = true })}); // HELLO — case-folded key
}
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 →