Skip to content

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 →