diff --git a/01_introduction_to_algorithms/zig/binary-search.zig b/01_introduction_to_algorithms/zig/binary-search.zig index f0e9123..5651314 100644 --- a/01_introduction_to_algorithms/zig/binary-search.zig +++ b/01_introduction_to_algorithms/zig/binary-search.zig @@ -11,12 +11,13 @@ pub fn main() void { fn binarySearch(comptime T: type, list: []const T, item: T) ?usize { var low: i32 = 0; - var high: i32 = @intCast(i32, list.len) - 1; + const u_high: u32 = @truncate(list.len); + var high: i32 = @intCast(u_high - 1); return while (low <= high) { - var mid = @divTrunc((low + high), 2); - var m = @intCast(usize, mid); - var guess = list[m]; + const mid = @divTrunc((low + high), 2); + const m: usize = @intCast(mid); + const guess = list[m]; if (guess == item) break m; if (guess > item) { high = mid - 1; diff --git a/02_selection_sort/zig/selection_sort.zig b/02_selection_sort/zig/selection_sort.zig index 19a2c10..e1f2994 100644 --- a/02_selection_sort/zig/selection_sort.zig +++ b/02_selection_sort/zig/selection_sort.zig @@ -10,12 +10,12 @@ pub fn main() !void { } fn selectionSort(comptime T: type, list: []T) void { - for (list) |_, i| { + for (0..list.len) |i| { var j = i + 1; while (j < list.len) : (j += 1) { if (list[i] > list[j]) { // swap - var tmp = list[i]; + const tmp = list[i]; list[i] = list[j]; list[j] = tmp; } @@ -30,6 +30,6 @@ test "selectionSort" { selectionSort(i32, s[0..]); try expect(s.len == exp.len); - for (s) |e, i| + for (s, 0..) |e, i| try expect(e == exp[i]); } diff --git a/03_recursion/zig/04_count.zig b/03_recursion/zig/04_count.zig index 914c4f2..487bb67 100644 --- a/03_recursion/zig/04_count.zig +++ b/03_recursion/zig/04_count.zig @@ -16,7 +16,7 @@ test "count" { var arr0 = [_]i32{}; var arr1 = [_]i32{42}; var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - var tests = [_]struct { + const tests = [_]struct { arr: []i32, exp: i32, }{ diff --git a/04_quicksort/zig/01_loop_sum.zig b/04_quicksort/zig/01_loop_sum.zig index 13c0d0a..a9b94e6 100644 --- a/04_quicksort/zig/01_loop_sum.zig +++ b/04_quicksort/zig/01_loop_sum.zig @@ -17,7 +17,7 @@ fn sum(comptime T: type, arr: []T) T { test "sum" { var arr0 = [_]i32{ 1, 2, 3, 4 }; var arr1 = [_]i32{}; - var tests = [_]struct { + const tests = [_]struct { arr: []i32, exp: i32, }{ @@ -32,7 +32,7 @@ test "sum" { }; for (tests) |t| { - var n = sum(@TypeOf(t.exp), t.arr); + const n = sum(@TypeOf(t.exp), t.arr); try expect(n == t.exp); } } diff --git a/04_quicksort/zig/02_recursive_sum.zig b/04_quicksort/zig/02_recursive_sum.zig index a015ceb..781cfe6 100644 --- a/04_quicksort/zig/02_recursive_sum.zig +++ b/04_quicksort/zig/02_recursive_sum.zig @@ -16,7 +16,7 @@ fn sum(comptime T: type, list: []T) T { test "sum" { var arr0 = [_]i32{ 1, 2, 3, 4 }; var arr1 = [_]i32{}; - var tests = [_]struct { + const tests = [_]struct { arr: []i32, exp: i32, }{ @@ -31,7 +31,7 @@ test "sum" { }; for (tests) |t| { - var n = sum(@TypeOf(t.exp), t.arr); + const n = sum(@TypeOf(t.exp), t.arr); try expect(n == t.exp); } } diff --git a/04_quicksort/zig/03_recursive_count.zig b/04_quicksort/zig/03_recursive_count.zig index 914c4f2..487bb67 100644 --- a/04_quicksort/zig/03_recursive_count.zig +++ b/04_quicksort/zig/03_recursive_count.zig @@ -16,7 +16,7 @@ test "count" { var arr0 = [_]i32{}; var arr1 = [_]i32{42}; var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - var tests = [_]struct { + const tests = [_]struct { arr: []i32, exp: i32, }{ diff --git a/04_quicksort/zig/05_quicksort.zig b/04_quicksort/zig/05_quicksort.zig index 83c78e3..6c99bb1 100644 --- a/04_quicksort/zig/05_quicksort.zig +++ b/04_quicksort/zig/05_quicksort.zig @@ -32,8 +32,8 @@ fn quicksort(comptime T: type, allocator: mem.Allocator, s: []const T) anyerror! } } - var low = try quicksort(T, allocator, lower.items); - var high = try quicksort(T, allocator, higher.items); + const low = try quicksort(T, allocator, lower.items); + const high = try quicksort(T, allocator, higher.items); var res = std.ArrayList(T).init(allocator); try res.appendSlice(low); @@ -44,13 +44,8 @@ fn quicksort(comptime T: type, allocator: mem.Allocator, s: []const T) anyerror! } test "quicksort" { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - var arena = heap.ArenaAllocator.init(gpa.allocator()); - defer { - arena.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return - } + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); const tests = [_]struct { s: []const u8, @@ -71,9 +66,9 @@ test "quicksort" { }; for (tests) |t| { - var res = try quicksort(u8, arena.allocator(), t.s); + const res = try quicksort(u8, arena.allocator(), t.s); try expect(res.len == t.exp.len); - for (res) |e, i| + for (res, 0..) |e, i| try expect(e == t.exp[i]); } } diff --git a/04_quicksort/zig/06_quicksort_parallel.zig b/04_quicksort/zig/06_quicksort_parallel.zig index 8c4991a..8c88c8a 100644 --- a/04_quicksort/zig/06_quicksort_parallel.zig +++ b/04_quicksort/zig/06_quicksort_parallel.zig @@ -4,32 +4,31 @@ const expect = std.testing.expect; const heap = std.heap; const mem = std.mem; -pub const io_mode = .evented; - -pub const Error = error{OutOfMemory}; +// pub const io_mode = .evented; pub fn main() !void { var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit(); - var s = [_]u8{ 5, 3, 6, 2, 10 }; + var u = [_]u8{ 5, 3, 6, 2, 10 }; - print("{d}\n", .{try quicksort(arena.allocator(), &s)}); + var s = try std.ArrayList(u8).initCapacity(arena.allocator(), u.len); + try quicksort(u8, arena.allocator(), &u, &s); + print("{d}\n", .{s.items}); } -// NOTE: this async version cannot be generic because allocating a frame for a -// generic function is not trivial. -fn quicksort(allocator: mem.Allocator, s: []const u8) Error![]const u8 { - if (s.len < 2) { - return s; +fn quicksort(comptime T: type, allocator: mem.Allocator, u: []const T, s: *std.ArrayList(T)) !void { + if (u.len < 2) { + try s.appendSlice(u); + return; } - var lower = std.ArrayList(u8).init(allocator); - var higher = std.ArrayList(u8).init(allocator); + var lower = std.ArrayList(T).init(allocator); + var higher = std.ArrayList(T).init(allocator); - const pivot = s[0]; - for (s[1..]) |item| { + const pivot = u[0]; + for (u[1..]) |item| { if (item <= pivot) { try lower.append(item); } else { @@ -37,27 +36,41 @@ fn quicksort(allocator: mem.Allocator, s: []const u8) Error![]const u8 { } } - const low_frame = try allocator.create(@Frame(quicksort)); - low_frame.* = async quicksort(allocator, lower.items); - var high = try quicksort(allocator, higher.items); - var low = try await low_frame; + // NOTE: zig has temporary removed the async/await syntax since v0.11.0 + // + // const low_frame = try allocator.create(@Frame(quicksort)); + // low_frame.* = async quicksort(allocator, lower.items); + // const high = try quicksort(allocator, higher.items); + // const low = try await low_frame; - var res = std.ArrayList(u8).init(allocator); - try res.appendSlice(low); - try res.append(pivot); - try res.appendSlice(high); + var low = try std.ArrayList(T).initCapacity(allocator, lower.items.len); + var high = try std.ArrayList(T).initCapacity(allocator, higher.items.len); - return res.items; + var low_handle = try std.Thread.spawn( + .{}, + quicksort, + .{ T, allocator, lower.items, &low }, + ); + var high_handle = try std.Thread.spawn( + .{}, + quicksort, + .{ T, allocator, higher.items, &high }, + ); + low_handle.join(); + high_handle.join(); + + const lows = try low.toOwnedSlice(); + const highs = try high.toOwnedSlice(); + try s.appendSlice(lows); + try s.append(pivot); + try s.appendSlice(highs); + + return; } test "quicksort" { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - var arena = heap.ArenaAllocator.init(gpa.allocator()); - defer { - arena.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return - } + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); const tests = [_]struct { s: []const u8, @@ -78,9 +91,10 @@ test "quicksort" { }; for (tests) |t| { - var res = try quicksort(arena.allocator(), t.s); - try expect(res.len == t.exp.len); - for (res) |e, i| - try expect(e == t.exp[i]); + var res = std.ArrayList(u8).init(arena.allocator()); + try quicksort(u8, arena.allocator(), t.s, &res); + try expect(res.items.len == t.exp.len); // length not matching + for (res.items, 0..) |e, i| + try expect(e == t.exp[i]); // element not matching } } diff --git a/06_breadth-first_search/zig/breadth_first_search.zig b/06_breadth-first_search/zig/breadth_first_search.zig index 2404c9e..c3b84cc 100644 --- a/06_breadth-first_search/zig/breadth_first_search.zig +++ b/06_breadth-first_search/zig/breadth_first_search.zig @@ -37,10 +37,10 @@ fn search( var arena = heap.ArenaAllocator.init(allocator); defer arena.deinit(); var searched = std.BufSet.init(arena.allocator()); - const Q = std.TailQueue([]const u8); + const Q = std.DoublyLinkedList([]const u8); var queue = Q{}; - var name_edges = graph.get(name); + const name_edges = graph.get(name); if (name_edges) |edges| { var nodes = try arena.allocator().alloc(Q.Node, edges.len); var i: usize = 0; @@ -53,28 +53,26 @@ fn search( } while (queue.len > 0) { - var first = queue.popFirst(); - if (first) |person| { - if (!searched.contains(person.data)) { - if (personIsSeller(person.data)) { - std.debug.print("{s} is a mango seller!\n", .{person.data}); - return; - } else { - var ee = graph.get(person.data); - if (ee) |edges| { - var nodes = try arena.allocator().alloc(Q.Node, edges.len); - var i: usize = 0; - while (i < edges.len) : (i += 1) { - nodes[i].data = edges[i]; - } - for (nodes) |*node| { - queue.append(node); - } - } - try searched.insert(person.data); - } + const person = queue.popFirst() orelse unreachable; // we always have at least one node if len > 0 + if (searched.contains(person.data)) { + continue; + } + if (personIsSeller(person.data)) { + std.debug.print("{s} is a mango seller!\n", .{person.data}); + return; + } + const ee = graph.get(person.data); + if (ee) |edges| { + var nodes = try arena.allocator().alloc(Q.Node, edges.len); + var i: usize = 0; + while (i < edges.len) : (i += 1) { + nodes[i].data = edges[i]; + } + for (nodes) |*node| { + queue.append(node); } } + try searched.insert(person.data); } } @@ -83,14 +81,9 @@ fn personIsSeller(name: []const u8) bool { } test "search" { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - - var graph = std.StringHashMap([][]const u8).init(gpa.allocator()); - defer { - graph.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return - } + const allocator = std.testing.allocator; + var graph = std.StringHashMap([][]const u8).init(allocator); + defer graph.deinit(); var you = [_][]const u8{ "alice", "bob", "claire" }; var bob = [_][]const u8{ "anuj", "peggy" }; @@ -110,5 +103,5 @@ test "search" { try graph.put("thom", &thom); try graph.put("jonny", &jonny); - try search(gpa.allocator(), &graph, "you"); + try search(allocator, &graph, "you"); } diff --git a/09_dijkstras_algorithm/Golang/01_dijkstras_algorithm.go b/09_dijkstras_algorithm/Golang/01_dijkstras_algorithm.go index 73b2274..0a5fa3c 100644 --- a/09_dijkstras_algorithm/Golang/01_dijkstras_algorithm.go +++ b/09_dijkstras_algorithm/Golang/01_dijkstras_algorithm.go @@ -47,7 +47,7 @@ func main() { // Go through all the neighbors of this node. neighbors := graph[node] - for node, _ := range neighbors { + for node := range neighbors { new_cost := cost + neighbors[node] // If it's cheaper to get to this neighbor by going through this node... if costs[node] > new_cost { @@ -71,7 +71,7 @@ func find_lowest_cost_node(costs map[string]float64) string { lowest_cost := math.Inf(1) lowest_cost_node := "" - for node, _ := range costs { + for node := range costs { // fmt.Println("Node:", node, "Value:", value) cost := costs[node] // If it's the lowest cost so far and hasn't been processed yet... diff --git a/09_dijkstras_algorithm/zig/dijkstras_algorithm.zig b/09_dijkstras_algorithm/zig/dijkstras_algorithm.zig index 2a038d0..62fe947 100644 --- a/09_dijkstras_algorithm/zig/dijkstras_algorithm.zig +++ b/09_dijkstras_algorithm/zig/dijkstras_algorithm.zig @@ -6,109 +6,109 @@ pub fn main() !void { var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit(); + const alloc = arena.allocator(); - var graph = std.StringHashMap(*std.StringHashMap(f32)).init(arena.allocator()); + var graph = std.StringHashMap(*std.StringHashMap(f32)).init(alloc); - var start = std.StringHashMap(f32).init(arena.allocator()); + var start = std.StringHashMap(f32).init(alloc); try start.put("a", 6); try start.put("b", 2); + try start.put("c", 42); try graph.put("start", &start); - var a = std.StringHashMap(f32).init(arena.allocator()); + var a = std.StringHashMap(f32).init(alloc); try a.put("finish", 1); try graph.put("a", &a); - var b = std.StringHashMap(f32).init(arena.allocator()); + var b = std.StringHashMap(f32).init(alloc); try b.put("a", 3); try b.put("finish", 5); try graph.put("b", &b); - var fin = std.StringHashMap(f32).init(arena.allocator()); + var c = std.StringHashMap(f32).init(alloc); + try c.put("finish", 42); + try graph.put("c", &c); + + var fin = std.StringHashMap(f32).init(alloc); try graph.put("finish", &fin); - var result = try dijkstra(arena.allocator(), &graph, "start", "finish"); + var costs, var path = try dijkstra(alloc, &graph, "start", "finish"); - std.debug.print("Cost from the start to each node:\n", .{}); - var costs_it = result.costs.iterator(); - while (costs_it.next()) |cost| { - std.debug.print("{s}: {d} ", .{ cost.key_ptr.*, cost.value_ptr.* }); + // Traverse the path hashmap backwards from finish to start and store the + // steps in an ordered list. + // The hashmap is unordered so there is no guarantee to print the path in + // the correct order only by iterating through key/value(s). + var dir = std.ArrayList([]const u8).init(alloc); + + var v: []const u8 = "finish"; + try dir.append(v); + var node = path.get(v); + while (node) |n| : (node = path.get(v)) { + try dir.append(n.?); + v = n.?; } - std.debug.print("\n", .{}); - std.debug.print("\n", .{}); std.debug.print("Path from start to finish:\n", .{}); - var path_it = result.path.iterator(); - while (path_it.next()) |parent| { - std.debug.print("{s} = {?s}\n", .{ parent.key_ptr.*, parent.value_ptr.* }); + std.debug.print("start =(", .{}); + var i = dir.items.len - 2; + var prev_cost: f32 = 0; + while (i > 0) : (i -= 1) { + const d = dir.items[i]; + const cost = costs.get(d).?; + std.debug.print("{d})=> {s:<6}: {d}\n{s:<5} =(", .{ cost - prev_cost, d, cost, d }); + prev_cost = cost; } + const fin_cost = costs.get("finish").?; + std.debug.print("{d})=> finish: {d}\n", .{ fin_cost - prev_cost, fin_cost }); } -/// this struct is needed because coercing an anonymous struct literal to an -/// error union is not supported by zig yet. Once this is fixed (with the -/// self-hosted compiler, see https://github.com/ziglang/zig/issues/11443), the -/// dijkstra function could just return: -/// ```zig -/// return { -/// .costs = costs, -/// .path = parents, -/// }; -/// ``` -const dijkstraResult = struct { - costs: std.StringHashMap(f32), - path: std.StringHashMap(?[]const u8), -}; - -/// applies the dijkstra algorithm on the provided graph using -/// the provided start anf finish nodes. +/// applies the dijkstra algorithm on graph using start and finish nodes. +/// Returns a tuple with the costs and the path. fn dijkstra( allocator: mem.Allocator, graph: *std.StringHashMap(*std.StringHashMap(f32)), start: []const u8, finish: []const u8, -) !dijkstraResult { +) !struct { + std.StringHashMap(f32), // costs + std.StringHashMap(?[]const u8), // path +} { var costs = std.StringHashMap(f32).init(allocator); var parents = std.StringHashMap(?[]const u8).init(allocator); - try costs.put(finish, std.math.inf_f32); + try costs.put(finish, std.math.inf(f32)); try parents.put(finish, null); // initialize costs and parents maps for the nodes having start as parent - var start_graph = graph.get(start); - if (start_graph) |sg| { - var it = sg.iterator(); - while (it.next()) |elem| { - try costs.put(elem.key_ptr.*, elem.value_ptr.*); - try parents.put(elem.key_ptr.*, start); - } + const start_graph = graph.get(start) orelse return error.MissingNode; + var sg_it = start_graph.iterator(); + while (sg_it.next()) |elem| { + try parents.put(elem.key_ptr.*, start); + try costs.put(elem.key_ptr.*, elem.value_ptr.*); } var processed = std.BufSet.init(allocator); var n = findCheapestNode(&costs, &processed); while (n) |node| : (n = findCheapestNode(&costs, &processed)) { - var cost = costs.get(node).?; - var neighbors = graph.get(node); - if (neighbors) |nbors| { - var it = nbors.iterator(); - while (it.next()) |neighbor| { - var new_cost = cost + neighbor.value_ptr.*; - if (costs.get(neighbor.key_ptr.*).? > new_cost) { - // update maps if we found a cheaper path - try costs.put(neighbor.key_ptr.*, new_cost); - try parents.put(neighbor.key_ptr.*, node); - } + const cost = costs.get(node).?; + const neighbors = graph.get(node) orelse return error.MissingNode; + var it = neighbors.iterator(); + while (it.next()) |neighbor| { + const new_cost = cost + neighbor.value_ptr.*; + if (costs.get(neighbor.key_ptr.*).? > new_cost) { + // update maps if we found a cheaper path + try costs.put(neighbor.key_ptr.*, new_cost); + try parents.put(neighbor.key_ptr.*, node); } } try processed.insert(node); } - return dijkstraResult{ - .costs = costs, - .path = parents, - }; + return .{ costs, parents }; } /// finds the cheapest node among the not yet processed ones. fn findCheapestNode(costs: *std.StringHashMap(f32), processed: *std.BufSet) ?[]const u8 { - var lowest_cost = std.math.inf_f32; + var lowest_cost = std.math.inf(f32); var lowest_cost_node: ?[]const u8 = null; var it = costs.iterator(); @@ -123,39 +123,35 @@ fn findCheapestNode(costs: *std.StringHashMap(f32), processed: *std.BufSet) ?[]c } test "dijkstra" { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - var arena = heap.ArenaAllocator.init(gpa.allocator()); - defer { - arena.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return - } + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); - var graph = std.StringHashMap(*std.StringHashMap(f32)).init(arena.allocator()); + var graph = std.StringHashMap(*std.StringHashMap(f32)).init(alloc); - var start = std.StringHashMap(f32).init(arena.allocator()); + var start = std.StringHashMap(f32).init(alloc); try start.put("a", 6); try start.put("b", 2); try graph.put("start", &start); - var a = std.StringHashMap(f32).init(arena.allocator()); + var a = std.StringHashMap(f32).init(alloc); try a.put("finish", 1); try graph.put("a", &a); - var b = std.StringHashMap(f32).init(arena.allocator()); + var b = std.StringHashMap(f32).init(alloc); try b.put("a", 3); try b.put("finish", 5); try graph.put("b", &b); - var fin = std.StringHashMap(f32).init(arena.allocator()); + var fin = std.StringHashMap(f32).init(alloc); try graph.put("finish", &fin); - var result = try dijkstra(arena.allocator(), &graph, "start", "finish"); + var costs, var path = try dijkstra(alloc, &graph, "start", "finish"); - try std.testing.expectEqual(result.costs.get("a").?, 5); - try std.testing.expectEqual(result.costs.get("b").?, 2); - try std.testing.expectEqual(result.costs.get("finish").?, 6); - try std.testing.expectEqual(result.path.get("b").?, "start"); - try std.testing.expectEqual(result.path.get("a").?, "b"); - try std.testing.expectEqual(result.path.get("finish").?, "a"); + try std.testing.expectEqual(costs.get("a").?, 5); + try std.testing.expectEqual(costs.get("b").?, 2); + try std.testing.expectEqual(costs.get("finish").?, 6); + try std.testing.expectEqual(path.get("b").?, "start"); + try std.testing.expectEqual(path.get("a").?, "b"); + try std.testing.expectEqual(path.get("finish").?, "a"); } diff --git a/10_greedy_algorithms/zig/set_covering.zig b/10_greedy_algorithms/zig/set_covering.zig index cf13ab3..e8431bf 100644 --- a/10_greedy_algorithms/zig/set_covering.zig +++ b/10_greedy_algorithms/zig/set_covering.zig @@ -7,43 +7,45 @@ pub fn main() !void { var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit(); - var states_needed_array = [_][]const u8{ "mt", "wa", "or", "id", "nv", "ut", "ca", "az" }; - var states_needed = std.BufSet.init(arena.allocator()); + const ally = arena.allocator(); + const states_needed_array = [_][]const u8{ "mt", "wa", "or", "id", "nv", "ut", "ca", "az" }; + var states_needed = std.BufSet.init(ally); for (states_needed_array) |sn| { try states_needed.insert(sn); } - var stations = std.StringHashMap(*std.BufSet).init(arena.allocator()); + var stations = std.StringHashMap(*std.BufSet).init(ally); - var kone = std.BufSet.init(arena.allocator()); - try kone.insert("id"); - try kone.insert("nv"); - try kone.insert("ut"); - try stations.put("kone", &kone); + var k_one = std.BufSet.init(ally); + try k_one.insert("id"); + try k_one.insert("nv"); + try k_one.insert("ut"); - var ktwo = std.BufSet.init(arena.allocator()); - try ktwo.insert("wa"); - try ktwo.insert("id"); - try ktwo.insert("mt"); - try stations.put("ktwo", &ktwo); + var k_two = std.BufSet.init(ally); + try k_two.insert("wa"); + try k_two.insert("id"); + try k_two.insert("mt"); - var kthree = std.BufSet.init(arena.allocator()); - try kthree.insert("or"); - try kthree.insert("nv"); - try kthree.insert("ca"); - try stations.put("kthree", &kthree); + var k_three = std.BufSet.init(ally); + try k_three.insert("or"); + try k_three.insert("nv"); + try k_three.insert("ca"); - var kfour = std.BufSet.init(arena.allocator()); - try kfour.insert("nv"); - try kfour.insert("ut"); - try stations.put("kfour", &kfour); + var k_four = std.BufSet.init(ally); + try k_four.insert("nv"); + try k_four.insert("ut"); - var kfive = std.BufSet.init(arena.allocator()); - try kfive.insert("ca"); - try kfive.insert("az"); - try stations.put("kfive", &kfive); + var k_five = std.BufSet.init(ally); + try k_five.insert("ca"); + try k_five.insert("az"); - var stations_covering = try setCovering(arena.allocator(), &stations, &states_needed); + try stations.put("kone", &k_one); + try stations.put("ktwo", &k_two); + try stations.put("kthree", &k_three); + try stations.put("kfour", &k_four); + try stations.put("kfive", &k_five); + + const stations_covering = try setCovering(ally, &stations, &states_needed); for (stations_covering) |sc| { std.debug.print("{s}\n", .{sc}); @@ -59,8 +61,8 @@ fn setCovering(allocator: mem.Allocator, stations: *std.StringHashMap(*std.BufSe var it = stations.iterator(); while (it.next()) |station| { - var covered = &std.ArrayList([]const u8).init(allocator); - try intersect(states_needed, station.value_ptr.*, covered); + var covered = std.ArrayList([]const u8).init(allocator); + try intersect(states_needed, station.value_ptr.*, &covered); if (covered.items.len > states_covered.len) { best_station = station.key_ptr.*; states_covered = covered.items; @@ -80,10 +82,61 @@ fn setCovering(allocator: mem.Allocator, stations: *std.StringHashMap(*std.BufSe return final_array.toOwnedSlice(); } +test "setCovering" { + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const ally = arena.allocator(); + + const states_needed_array = [_][]const u8{ "mt", "wa", "or", "id", "nv", "ut", "ca", "az" }; + var states_needed = std.BufSet.init(ally); + for (states_needed_array) |sn| { + try states_needed.insert(sn); + } + + var stations = std.StringHashMap(*std.BufSet).init(ally); + + var kone = std.BufSet.init(ally); + try kone.insert("id"); + try kone.insert("nv"); + try kone.insert("ut"); + try stations.put("kone", &kone); + + var ktwo = std.BufSet.init(ally); + try ktwo.insert("wa"); + try ktwo.insert("id"); + try ktwo.insert("mt"); + try stations.put("ktwo", &ktwo); + + var kthree = std.BufSet.init(ally); + try kthree.insert("or"); + try kthree.insert("nv"); + try kthree.insert("ca"); + try stations.put("kthree", &kthree); + + var kfour = std.BufSet.init(ally); + try kfour.insert("nv"); + try kfour.insert("ut"); + try stations.put("kfour", &kfour); + + var kfive = std.BufSet.init(ally); + try kfive.insert("ca"); + try kfive.insert("az"); + try stations.put("kfive", &kfive); + + const stations_covering = try setCovering(ally, &stations, &states_needed); + + // The order of the keys in the hashmap affects the final result. + // StringHashMap always produces the same order and we can assert over it. + const expectedStations = &[_][]const u8{ "kfour", "ktwo", "kthree", "kfive" }; + for (stations_covering, 0..) |sc, i| { + try std.testing.expectEqualStrings(expectedStations[i], sc); + } +} + fn intersect(left: *std.BufSet, right: *std.BufSet, intersection: *std.ArrayList([]const u8)) !void { var l_it = left.iterator(); - var r_it = right.iterator(); while (l_it.next()) |l| { + var r_it = right.iterator(); while (r_it.next()) |r| { if (std.mem.eql(u8, l.*, r.*)) { try intersection.append(l.*); @@ -92,6 +145,54 @@ fn intersect(left: *std.BufSet, right: *std.BufSet, intersection: *std.ArrayList } } +test "intersect" { + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const ally = arena.allocator(); + + var left = std.BufSet.init(ally); + try left.insert("banana"); + try left.insert("mango"); + try left.insert("papaya"); + + var right = std.BufSet.init(ally); + try right.insert("banana"); + try right.insert("mango"); + try right.insert("avocado"); + + { + // partial intersection + const expected = &[2][]const u8{ "banana", "mango" }; + var actual = std.ArrayList([]const u8).init(ally); + + try intersect(&left, &right, &actual); + + for (actual.items, expected) |a, e| { + try std.testing.expectEqualStrings(e, a); + } + } + { + // full intersection + const expected = &[3][]const u8{ "banana", "mango", "papaya" }; + var actual = std.ArrayList([]const u8).init(ally); + + try intersect(&left, &left, &actual); + + for (actual.items, expected) |a, e| { + try std.testing.expectEqualStrings(e, a); + } + } + { + // no intersection + var empty = std.BufSet.init(ally); + var actual = std.ArrayList([]const u8).init(ally); + + try intersect(&left, &empty, &actual); + + try std.testing.expect(actual.items.len == 0); + } +} + fn difference(lessening: *std.BufSet, subtracting: [][]const u8) void { var less_it = lessening.iterator(); @@ -104,55 +205,52 @@ fn difference(lessening: *std.BufSet, subtracting: [][]const u8) void { } } -test "setCovering" { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - var arena = heap.ArenaAllocator.init(gpa.allocator()); - defer { - arena.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return +test "difference" { + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const ally = arena.allocator(); + + { + // partial diff + var less = std.BufSet.init(ally); + try less.insert("banana"); + try less.insert("mango"); + try less.insert("papaya"); + + var sub = [_][]const u8{ "banana", "mango" }; + + difference(&less, &sub); + + try std.testing.expect(less.count() == 1); + try std.testing.expect(less.contains("papaya")); } + { + // full diff + var less = std.BufSet.init(ally); + try less.insert("banana"); + try less.insert("mango"); + try less.insert("papaya"); - var states_needed_array = [_][]const u8{ "mt", "wa", "or", "id", "nv", "ut", "ca", "az" }; - var states_needed = std.BufSet.init(arena.allocator()); - for (states_needed_array) |sn| { - try states_needed.insert(sn); + var sub = [_][]const u8{ "avocado", "kiwi", "ananas" }; + + difference(&less, &sub); + + try std.testing.expect(less.count() == 3); + try std.testing.expect(less.contains("banana")); + try std.testing.expect(less.contains("mango")); + try std.testing.expect(less.contains("papaya")); } + { + // no diff + var less = std.BufSet.init(ally); + try less.insert("banana"); + try less.insert("mango"); + try less.insert("papaya"); - var stations = std.StringHashMap(*std.BufSet).init(arena.allocator()); + var sub = [_][]const u8{ "mango", "papaya", "banana" }; - var kone = std.BufSet.init(arena.allocator()); - try kone.insert("id"); - try kone.insert("nv"); - try kone.insert("ut"); - try stations.put("kone", &kone); + difference(&less, &sub); - var ktwo = std.BufSet.init(arena.allocator()); - try ktwo.insert("wa"); - try ktwo.insert("id"); - try ktwo.insert("mt"); - try stations.put("ktwo", &ktwo); - - var kthree = std.BufSet.init(arena.allocator()); - try kthree.insert("or"); - try kthree.insert("nv"); - try kthree.insert("ca"); - try stations.put("kthree", &kthree); - - var kfour = std.BufSet.init(arena.allocator()); - try kfour.insert("nv"); - try kfour.insert("ut"); - try stations.put("kfour", &kfour); - - var kfive = std.BufSet.init(arena.allocator()); - try kfive.insert("ca"); - try kfive.insert("az"); - try stations.put("kfive", &kfive); - - var stations_covering = try setCovering(arena.allocator(), &stations, &states_needed); - - var expectedStations = &[_][]const u8{ "kone", "ktwo", "kfive", "kthree" }; - for (stations_covering) |sc, i| { - try std.testing.expectEqualStrings(expectedStations[i], sc); + try std.testing.expect(less.count() == 0); } } diff --git a/11_dynamic_programming/zig/longest_common_subsequence.zig b/11_dynamic_programming/zig/longest_common_subsequence.zig index b0d5909..8ad7100 100644 --- a/11_dynamic_programming/zig/longest_common_subsequence.zig +++ b/11_dynamic_programming/zig/longest_common_subsequence.zig @@ -2,18 +2,20 @@ const std = @import("std"); const heap = std.heap; const math = std.math; const expect = std.testing.expect; +const expectEqualStrings = std.testing.expectEqualStrings; pub fn main() !void { var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit(); - var n = try subsequence(arena.allocator(), "fish", "fosh"); - std.debug.print("{d}\n", .{n}); + const n, const sub = try subsequence(arena.allocator(), "fish", "fosh"); + std.debug.print("{d}: {s}\n", .{ n, sub }); } -fn subsequence(allocator: std.mem.Allocator, a: []const u8, b: []const u8) !u32 { +fn subsequence(allocator: std.mem.Allocator, a: []const u8, b: []const u8) !struct { u32, []const u8 } { var grid = try allocator.alloc([]u32, a.len + 1); + var subseq = try std.ArrayList(u8).initCapacity(allocator, @max(a.len, b.len)); for (grid) |*row| { row.* = try allocator.alloc(u32, b.len + 1); @@ -28,36 +30,34 @@ fn subsequence(allocator: std.mem.Allocator, a: []const u8, b: []const u8) !u32 while (j <= b.len) : (j += 1) { if (a[i - 1] == b[j - 1]) { grid[i][j] = grid[i - 1][j - 1] + 1; + try subseq.append(a[i - 1]); } else { - grid[i][j] = math.max(grid[i][j - 1], grid[i - 1][j]); + grid[i][j] = @max(grid[i][j - 1], grid[i - 1][j]); } } } - return grid[a.len][b.len]; + const sub = try subseq.toOwnedSlice(); + return .{ grid[a.len][b.len], sub }; } test "subsequence" { - var tests = [_]struct { + const tests = [_]struct { a: []const u8, b: []const u8, - exp: u32, + expected: struct { u32, []const u8 }, }{ - .{ .a = "abc", .b = "abcd", .exp = 3 }, - .{ .a = "pera", .b = "mela", .exp = 2 }, - .{ .a = "banana", .b = "kiwi", .exp = 0 }, + .{ .a = "abc", .b = "abcd", .expected = .{ 3, "abc" } }, + .{ .a = "pera", .b = "mela", .expected = .{ 2, "ea" } }, + .{ .a = "banana", .b = "kiwi", .expected = .{ 0, "" } }, }; for (tests) |t| { - var gpa = heap.GeneralPurposeAllocator(.{}){}; - var arena = heap.ArenaAllocator.init(gpa.allocator()); - defer { - arena.deinit(); - const leaked = gpa.deinit(); - if (leaked) std.testing.expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return - } + var arena = heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); - var n = try subsequence(arena.allocator(), t.a, t.b); - try expect(n == t.exp); + const actual = try subsequence(arena.allocator(), t.a, t.b); + + try std.testing.expectEqualDeep(t.expected, actual); } }