Skip to content

Set Operations

The setops module provides mathematical set operations for 1D arrays, treating them as sets of unique elements. Operations include finding unique elements, intersections, unions, differences, and membership testing.

Unique Elements

Find unique elements in an array:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Array with duplicates
    const shape = [_]usize{10};
    const data = [_]i32{ 1, 2, 3, 2, 4, 1, 5, 3, 6, 2 };
    var array = num.core.NDArray(i32).init(&shape, @constCast(&data));
    
    var unique_vals = try num.setops.unique(allocator, i32, array);
    defer unique_vals.deinit(allocator);
    
    std.debug.print("Original: {any}\n", .{data});
    std.debug.print("Unique: {any}\n", .{unique_vals.data});
}
// Output:
// Original: [1, 2, 3, 2, 4, 1, 5, 3, 6, 2]
// Unique: [1, 2, 3, 4, 5, 6]

Element Membership (in1d)

Test whether elements of one array are in another:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Test array
    const shape1 = [_]usize{5};
    const data1 = [_]i32{ 1, 3, 5, 7, 9 };
    var ar1 = num.core.NDArray(i32).init(&shape1, @constCast(&data1));
    
    // Reference set
    const shape2 = [_]usize{7};
    const data2 = [_]i32{ 2, 3, 5, 7, 11, 13, 17 };
    var ar2 = num.core.NDArray(i32).init(&shape2, @constCast(&data2));
    
    var membership = try num.setops.in1d(allocator, i32, ar1, ar2);
    defer membership.deinit(allocator);
    
    std.debug.print("Test values: {any}\n", .{data1});
    std.debug.print("In set: {any}\n", .{membership.data});
}
// Output:
// Test values: [1, 3, 5, 7, 9]
// In set: [false, true, true, true, false]
// (3, 5, 7 are in the reference set)

Intersection (intersect1d)

Find common elements between two arrays:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Set A: prime numbers under 20
    const shape_a = [_]usize{8};
    const data_a = [_]i32{ 2, 3, 5, 7, 11, 13, 17, 19 };
    var set_a = num.core.NDArray(i32).init(&shape_a, @constCast(&data_a));
    
    // Set B: Fibonacci numbers under 20
    const shape_b = [_]usize{7};
    const data_b = [_]i32{ 1, 1, 2, 3, 5, 8, 13 };
    var set_b = num.core.NDArray(i32).init(&shape_b, @constCast(&data_b));
    
    var intersection = try num.setops.intersect1d(allocator, i32, set_a, set_b);
    defer intersection.deinit(allocator);
    
    std.debug.print("Primes: {any}\n", .{data_a});
    std.debug.print("Fibonacci: {any}\n", .{data_b});
    std.debug.print("Intersection: {any}\n", .{intersection.data});
}
// Output:
// Primes: [2, 3, 5, 7, 11, 13, 17, 19]
// Fibonacci: [1, 1, 2, 3, 5, 8, 13]
// Intersection: [2, 3, 5, 13]

Union (union1d)

Find all unique elements from both arrays:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const shape_a = [_]usize{5};
    const data_a = [_]i32{ 1, 2, 3, 4, 5 };
    var set_a = num.core.NDArray(i32).init(&shape_a, @constCast(&data_a));
    
    const shape_b = [_]usize{5};
    const data_b = [_]i32{ 4, 5, 6, 7, 8 };
    var set_b = num.core.NDArray(i32).init(&shape_b, @constCast(&data_b));
    
    var union_set = try num.setops.union1d(allocator, i32, set_a, set_b);
    defer union_set.deinit(allocator);
    
    std.debug.print("Set A: {any}\n", .{data_a});
    std.debug.print("Set B: {any}\n", .{data_b});
    std.debug.print("Union: {any}\n", .{union_set.data});
}
// Output:
// Set A: [1, 2, 3, 4, 5]
// Set B: [4, 5, 6, 7, 8]
// Union: [1, 2, 3, 4, 5, 6, 7, 8]

Set Difference (setdiff1d)

Find elements in first array that are not in second:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // All students
    const shape_all = [_]usize{8};
    const data_all = [_]i32{ 101, 102, 103, 104, 105, 106, 107, 108 };
    var all_students = num.core.NDArray(i32).init(&shape_all, @constCast(&data_all));
    
    // Students who passed
    const shape_passed = [_]usize{5};
    const data_passed = [_]i32{ 102, 104, 105, 107, 108 };
    var passed = num.core.NDArray(i32).init(&shape_passed, @constCast(&data_passed));
    
    var failed = try num.setops.setdiff1d(allocator, i32, all_students, passed);
    defer failed.deinit(allocator);
    
    std.debug.print("All students: {any}\n", .{data_all});
    std.debug.print("Passed: {any}\n", .{data_passed});
    std.debug.print("Failed: {any}\n", .{failed.data});
}
// Output:
// All students: [101, 102, 103, 104, 105, 106, 107, 108]
// Passed: [102, 104, 105, 107, 108]
// Failed: [101, 103, 106]

Symmetric Difference (setxor1d)

Find elements in either array but not in both:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Group A preferences
    const shape_a = [_]usize{6};
    const data_a = [_]i32{ 1, 2, 3, 4, 5, 6 };
    var group_a = num.core.NDArray(i32).init(&shape_a, @constCast(&data_a));
    
    // Group B preferences
    const shape_b = [_]usize{6};
    const data_b = [_]i32{ 4, 5, 6, 7, 8, 9 };
    var group_b = num.core.NDArray(i32).init(&shape_b, @constCast(&data_b));
    
    var exclusive = try num.setops.setxor1d(allocator, i32, group_a, group_b);
    defer exclusive.deinit(allocator);
    
    std.debug.print("Group A: {any}\n", .{data_a});
    std.debug.print("Group B: {any}\n", .{data_b});
    std.debug.print("Exclusive choices: {any}\n", .{exclusive.data});
}
// Output:
// Group A: [1, 2, 3, 4, 5, 6]
// Group B: [4, 5, 6, 7, 8, 9]
// Exclusive choices: [1, 2, 3, 7, 8, 9]
// (Elements unique to each group)

Practical Example: Data Validation

Validate that all required IDs are present:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Required product IDs
    const shape_req = [_]usize{5};
    const data_req = [_]i32{ 1001, 1002, 1003, 1004, 1005 };
    var required = num.core.NDArray(i32).init(&shape_req, @constCast(&data_req));
    
    // Received product IDs
    const shape_recv = [_]usize{6};
    const data_recv = [_]i32{ 1001, 1002, 1003, 1006, 1007, 1008 };
    var received = num.core.NDArray(i32).init(&shape_recv, @constCast(&data_recv));
    
    // Find missing items
    var missing = try num.setops.setdiff1d(allocator, i32, required, received);
    defer missing.deinit(allocator);
    
    // Find extra items
    var extra = try num.setops.setdiff1d(allocator, i32, received, required);
    defer extra.deinit(allocator);
    
    std.debug.print("Validation Report:\n", .{});
    std.debug.print("  Required: {any}\n", .{data_req});
    std.debug.print("  Received: {any}\n", .{data_recv});
    std.debug.print("  Missing: {any}\n", .{missing.data});
    std.debug.print("  Extra: {any}\n", .{extra.data});
}
// Output:
// Validation Report:
//   Required: [1001, 1002, 1003, 1004, 1005]
//   Received: [1001, 1002, 1003, 1006, 1007, 1008]
//   Missing: [1004, 1005]
//   Extra: [1006, 1007, 1008]

Practical Example: Category Analysis

Analyze overlap between different categories:

zig
const std = @import("std");
const num = @import("num");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Customer segments
    const shape_premium = [_]usize{6};
    const data_premium = [_]i32{ 101, 102, 103, 104, 105, 106 };
    var premium_customers = num.core.NDArray(i32).init(&shape_premium, @constCast(&data_premium));
    
    const shape_active = [_]usize{8};
    const data_active = [_]i32{ 102, 104, 105, 107, 108, 109, 110, 111 };
    var active_customers = num.core.NDArray(i32).init(&shape_active, @constCast(&data_active));
    
    // Premium AND Active
    var premium_active = try num.setops.intersect1d(allocator, i32, premium_customers, active_customers);
    defer premium_active.deinit(allocator);
    
    // Premium OR Active
    var any_segment = try num.setops.union1d(allocator, i32, premium_customers, active_customers);
    defer any_segment.deinit(allocator);
    
    // Premium but NOT Active
    var premium_only = try num.setops.setdiff1d(allocator, i32, premium_customers, active_customers);
    defer premium_only.deinit(allocator);
    
    std.debug.print("Customer Segmentation:\n", .{});
    std.debug.print("  Premium & Active: {d} customers\n", .{premium_active.shape[0]});
    std.debug.print("  Total in segments: {d} customers\n", .{any_segment.shape[0]});
    std.debug.print("  Premium only: {any}\n", .{premium_only.data});
}
// Output:
// Customer Segmentation:
//   Premium & Active: 3 customers
//   Total in segments: 11 customers
//   Premium only: [101, 103, 106]

Released under the Apache 2.0 License.