Update examples for Zig (#287)

* update zig in chapters 1-6

* fix zig dijkstras algo

* fix zig greedy algo

* fix longest_common_subsequence in zig

* cleanup

* test: use testing allocator
This commit is contained in:
Paolo Grisoli
2024-12-07 14:29:48 +01:00
committed by GitHub
parent 177581a9a4
commit 8a13efde83
13 changed files with 357 additions and 260 deletions

View File

@@ -11,12 +11,13 @@ pub fn main() void {
fn binarySearch(comptime T: type, list: []const T, item: T) ?usize { fn binarySearch(comptime T: type, list: []const T, item: T) ?usize {
var low: i32 = 0; 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) { return while (low <= high) {
var mid = @divTrunc((low + high), 2); const mid = @divTrunc((low + high), 2);
var m = @intCast(usize, mid); const m: usize = @intCast(mid);
var guess = list[m]; const guess = list[m];
if (guess == item) break m; if (guess == item) break m;
if (guess > item) { if (guess > item) {
high = mid - 1; high = mid - 1;

View File

@@ -10,12 +10,12 @@ pub fn main() !void {
} }
fn selectionSort(comptime T: type, list: []T) void { fn selectionSort(comptime T: type, list: []T) void {
for (list) |_, i| { for (0..list.len) |i| {
var j = i + 1; var j = i + 1;
while (j < list.len) : (j += 1) { while (j < list.len) : (j += 1) {
if (list[i] > list[j]) { if (list[i] > list[j]) {
// swap // swap
var tmp = list[i]; const tmp = list[i];
list[i] = list[j]; list[i] = list[j];
list[j] = tmp; list[j] = tmp;
} }
@@ -30,6 +30,6 @@ test "selectionSort" {
selectionSort(i32, s[0..]); selectionSort(i32, s[0..]);
try expect(s.len == exp.len); try expect(s.len == exp.len);
for (s) |e, i| for (s, 0..) |e, i|
try expect(e == exp[i]); try expect(e == exp[i]);
} }

View File

@@ -16,7 +16,7 @@ test "count" {
var arr0 = [_]i32{}; var arr0 = [_]i32{};
var arr1 = [_]i32{42}; var arr1 = [_]i32{42};
var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var tests = [_]struct { const tests = [_]struct {
arr: []i32, arr: []i32,
exp: i32, exp: i32,
}{ }{

View File

@@ -17,7 +17,7 @@ fn sum(comptime T: type, arr: []T) T {
test "sum" { test "sum" {
var arr0 = [_]i32{ 1, 2, 3, 4 }; var arr0 = [_]i32{ 1, 2, 3, 4 };
var arr1 = [_]i32{}; var arr1 = [_]i32{};
var tests = [_]struct { const tests = [_]struct {
arr: []i32, arr: []i32,
exp: i32, exp: i32,
}{ }{
@@ -32,7 +32,7 @@ test "sum" {
}; };
for (tests) |t| { for (tests) |t| {
var n = sum(@TypeOf(t.exp), t.arr); const n = sum(@TypeOf(t.exp), t.arr);
try expect(n == t.exp); try expect(n == t.exp);
} }
} }

View File

@@ -16,7 +16,7 @@ fn sum(comptime T: type, list: []T) T {
test "sum" { test "sum" {
var arr0 = [_]i32{ 1, 2, 3, 4 }; var arr0 = [_]i32{ 1, 2, 3, 4 };
var arr1 = [_]i32{}; var arr1 = [_]i32{};
var tests = [_]struct { const tests = [_]struct {
arr: []i32, arr: []i32,
exp: i32, exp: i32,
}{ }{
@@ -31,7 +31,7 @@ test "sum" {
}; };
for (tests) |t| { for (tests) |t| {
var n = sum(@TypeOf(t.exp), t.arr); const n = sum(@TypeOf(t.exp), t.arr);
try expect(n == t.exp); try expect(n == t.exp);
} }
} }

View File

@@ -16,7 +16,7 @@ test "count" {
var arr0 = [_]i32{}; var arr0 = [_]i32{};
var arr1 = [_]i32{42}; var arr1 = [_]i32{42};
var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var arr2 = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var tests = [_]struct { const tests = [_]struct {
arr: []i32, arr: []i32,
exp: i32, exp: i32,
}{ }{

View File

@@ -32,8 +32,8 @@ fn quicksort(comptime T: type, allocator: mem.Allocator, s: []const T) anyerror!
} }
} }
var low = try quicksort(T, allocator, lower.items); const low = try quicksort(T, allocator, lower.items);
var high = try quicksort(T, allocator, higher.items); const high = try quicksort(T, allocator, higher.items);
var res = std.ArrayList(T).init(allocator); var res = std.ArrayList(T).init(allocator);
try res.appendSlice(low); try res.appendSlice(low);
@@ -44,13 +44,8 @@ fn quicksort(comptime T: type, allocator: mem.Allocator, s: []const T) anyerror!
} }
test "quicksort" { test "quicksort" {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(std.testing.allocator);
var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit();
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
}
const tests = [_]struct { const tests = [_]struct {
s: []const u8, s: []const u8,
@@ -71,9 +66,9 @@ test "quicksort" {
}; };
for (tests) |t| { 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); try expect(res.len == t.exp.len);
for (res) |e, i| for (res, 0..) |e, i|
try expect(e == t.exp[i]); try expect(e == t.exp[i]);
} }
} }

View File

@@ -4,32 +4,31 @@ const expect = std.testing.expect;
const heap = std.heap; const heap = std.heap;
const mem = std.mem; const mem = std.mem;
pub const io_mode = .evented; // pub const io_mode = .evented;
pub const Error = error{OutOfMemory};
pub fn main() !void { pub fn main() !void {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var gpa = heap.GeneralPurposeAllocator(.{}){};
var arena = heap.ArenaAllocator.init(gpa.allocator()); var arena = heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); 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 fn quicksort(comptime T: type, allocator: mem.Allocator, u: []const T, s: *std.ArrayList(T)) !void {
// generic function is not trivial. if (u.len < 2) {
fn quicksort(allocator: mem.Allocator, s: []const u8) Error![]const u8 { try s.appendSlice(u);
if (s.len < 2) { return;
return s;
} }
var lower = std.ArrayList(u8).init(allocator); var lower = std.ArrayList(T).init(allocator);
var higher = std.ArrayList(u8).init(allocator); var higher = std.ArrayList(T).init(allocator);
const pivot = s[0]; const pivot = u[0];
for (s[1..]) |item| { for (u[1..]) |item| {
if (item <= pivot) { if (item <= pivot) {
try lower.append(item); try lower.append(item);
} else { } else {
@@ -37,27 +36,41 @@ fn quicksort(allocator: mem.Allocator, s: []const u8) Error![]const u8 {
} }
} }
const low_frame = try allocator.create(@Frame(quicksort)); // NOTE: zig has temporary removed the async/await syntax since v0.11.0
low_frame.* = async quicksort(allocator, lower.items); //
var high = try quicksort(allocator, higher.items); // const low_frame = try allocator.create(@Frame(quicksort));
var low = try await low_frame; // 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); var low = try std.ArrayList(T).initCapacity(allocator, lower.items.len);
try res.appendSlice(low); var high = try std.ArrayList(T).initCapacity(allocator, higher.items.len);
try res.append(pivot);
try res.appendSlice(high);
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" { test "quicksort" {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(std.testing.allocator);
var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit();
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
}
const tests = [_]struct { const tests = [_]struct {
s: []const u8, s: []const u8,
@@ -78,9 +91,10 @@ test "quicksort" {
}; };
for (tests) |t| { for (tests) |t| {
var res = try quicksort(arena.allocator(), t.s); var res = std.ArrayList(u8).init(arena.allocator());
try expect(res.len == t.exp.len); try quicksort(u8, arena.allocator(), t.s, &res);
for (res) |e, i| try expect(res.items.len == t.exp.len); // length not matching
try expect(e == t.exp[i]); for (res.items, 0..) |e, i|
try expect(e == t.exp[i]); // element not matching
} }
} }

View File

@@ -37,10 +37,10 @@ fn search(
var arena = heap.ArenaAllocator.init(allocator); var arena = heap.ArenaAllocator.init(allocator);
defer arena.deinit(); defer arena.deinit();
var searched = std.BufSet.init(arena.allocator()); var searched = std.BufSet.init(arena.allocator());
const Q = std.TailQueue([]const u8); const Q = std.DoublyLinkedList([]const u8);
var queue = Q{}; var queue = Q{};
var name_edges = graph.get(name); const name_edges = graph.get(name);
if (name_edges) |edges| { if (name_edges) |edges| {
var nodes = try arena.allocator().alloc(Q.Node, edges.len); var nodes = try arena.allocator().alloc(Q.Node, edges.len);
var i: usize = 0; var i: usize = 0;
@@ -53,28 +53,26 @@ fn search(
} }
while (queue.len > 0) { while (queue.len > 0) {
var first = queue.popFirst(); const person = queue.popFirst() orelse unreachable; // we always have at least one node if len > 0
if (first) |person| { if (searched.contains(person.data)) {
if (!searched.contains(person.data)) { continue;
if (personIsSeller(person.data)) { }
std.debug.print("{s} is a mango seller!\n", .{person.data}); if (personIsSeller(person.data)) {
return; std.debug.print("{s} is a mango seller!\n", .{person.data});
} else { return;
var ee = graph.get(person.data); }
if (ee) |edges| { const ee = graph.get(person.data);
var nodes = try arena.allocator().alloc(Q.Node, edges.len); if (ee) |edges| {
var i: usize = 0; var nodes = try arena.allocator().alloc(Q.Node, edges.len);
while (i < edges.len) : (i += 1) { var i: usize = 0;
nodes[i].data = edges[i]; while (i < edges.len) : (i += 1) {
} nodes[i].data = edges[i];
for (nodes) |*node| { }
queue.append(node); for (nodes) |*node| {
} queue.append(node);
}
try searched.insert(person.data);
}
} }
} }
try searched.insert(person.data);
} }
} }
@@ -83,14 +81,9 @@ fn personIsSeller(name: []const u8) bool {
} }
test "search" { test "search" {
var gpa = heap.GeneralPurposeAllocator(.{}){}; const allocator = std.testing.allocator;
var graph = std.StringHashMap([][]const u8).init(allocator);
var graph = std.StringHashMap([][]const u8).init(gpa.allocator()); defer graph.deinit();
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
}
var you = [_][]const u8{ "alice", "bob", "claire" }; var you = [_][]const u8{ "alice", "bob", "claire" };
var bob = [_][]const u8{ "anuj", "peggy" }; var bob = [_][]const u8{ "anuj", "peggy" };
@@ -110,5 +103,5 @@ test "search" {
try graph.put("thom", &thom); try graph.put("thom", &thom);
try graph.put("jonny", &jonny); try graph.put("jonny", &jonny);
try search(gpa.allocator(), &graph, "you"); try search(allocator, &graph, "you");
} }

View File

@@ -47,7 +47,7 @@ func main() {
// Go through all the neighbors of this node. // Go through all the neighbors of this node.
neighbors := graph[node] neighbors := graph[node]
for node, _ := range neighbors { for node := range neighbors {
new_cost := cost + neighbors[node] new_cost := cost + neighbors[node]
// If it's cheaper to get to this neighbor by going through this node... // If it's cheaper to get to this neighbor by going through this node...
if costs[node] > new_cost { 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 := math.Inf(1)
lowest_cost_node := "" lowest_cost_node := ""
for node, _ := range costs { for node := range costs {
// fmt.Println("Node:", node, "Value:", value) // fmt.Println("Node:", node, "Value:", value)
cost := costs[node] cost := costs[node]
// If it's the lowest cost so far and hasn't been processed yet... // If it's the lowest cost so far and hasn't been processed yet...

View File

@@ -6,109 +6,109 @@ pub fn main() !void {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var gpa = heap.GeneralPurposeAllocator(.{}){};
var arena = heap.ArenaAllocator.init(gpa.allocator()); var arena = heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); 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("a", 6);
try start.put("b", 2); try start.put("b", 2);
try start.put("c", 42);
try graph.put("start", &start); 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 a.put("finish", 1);
try graph.put("a", &a); 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("a", 3);
try b.put("finish", 5); try b.put("finish", 5);
try graph.put("b", &b); 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); 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", .{}); // Traverse the path hashmap backwards from finish to start and store the
var costs_it = result.costs.iterator(); // steps in an ordered list.
while (costs_it.next()) |cost| { // The hashmap is unordered so there is no guarantee to print the path in
std.debug.print("{s}: {d} ", .{ cost.key_ptr.*, cost.value_ptr.* }); // 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", .{}); std.debug.print("Path from start to finish:\n", .{});
var path_it = result.path.iterator(); std.debug.print("start =(", .{});
while (path_it.next()) |parent| { var i = dir.items.len - 2;
std.debug.print("{s} = {?s}\n", .{ parent.key_ptr.*, parent.value_ptr.* }); 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 /// applies the dijkstra algorithm on graph using start and finish nodes.
/// error union is not supported by zig yet. Once this is fixed (with the /// Returns a tuple with the costs and the path.
/// 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.
fn dijkstra( fn dijkstra(
allocator: mem.Allocator, allocator: mem.Allocator,
graph: *std.StringHashMap(*std.StringHashMap(f32)), graph: *std.StringHashMap(*std.StringHashMap(f32)),
start: []const u8, start: []const u8,
finish: []const u8, finish: []const u8,
) !dijkstraResult { ) !struct {
std.StringHashMap(f32), // costs
std.StringHashMap(?[]const u8), // path
} {
var costs = std.StringHashMap(f32).init(allocator); var costs = std.StringHashMap(f32).init(allocator);
var parents = std.StringHashMap(?[]const u8).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); try parents.put(finish, null);
// initialize costs and parents maps for the nodes having start as parent // initialize costs and parents maps for the nodes having start as parent
var start_graph = graph.get(start); const start_graph = graph.get(start) orelse return error.MissingNode;
if (start_graph) |sg| { var sg_it = start_graph.iterator();
var it = sg.iterator(); while (sg_it.next()) |elem| {
while (it.next()) |elem| { try parents.put(elem.key_ptr.*, start);
try costs.put(elem.key_ptr.*, elem.value_ptr.*); try costs.put(elem.key_ptr.*, elem.value_ptr.*);
try parents.put(elem.key_ptr.*, start);
}
} }
var processed = std.BufSet.init(allocator); var processed = std.BufSet.init(allocator);
var n = findCheapestNode(&costs, &processed); var n = findCheapestNode(&costs, &processed);
while (n) |node| : (n = findCheapestNode(&costs, &processed)) { while (n) |node| : (n = findCheapestNode(&costs, &processed)) {
var cost = costs.get(node).?; const cost = costs.get(node).?;
var neighbors = graph.get(node); const neighbors = graph.get(node) orelse return error.MissingNode;
if (neighbors) |nbors| { var it = neighbors.iterator();
var it = nbors.iterator(); while (it.next()) |neighbor| {
while (it.next()) |neighbor| { const new_cost = cost + neighbor.value_ptr.*;
var new_cost = cost + neighbor.value_ptr.*; if (costs.get(neighbor.key_ptr.*).? > new_cost) {
if (costs.get(neighbor.key_ptr.*).? > new_cost) { // update maps if we found a cheaper path
// update maps if we found a cheaper path try costs.put(neighbor.key_ptr.*, new_cost);
try costs.put(neighbor.key_ptr.*, new_cost); try parents.put(neighbor.key_ptr.*, node);
try parents.put(neighbor.key_ptr.*, node);
}
} }
} }
try processed.insert(node); try processed.insert(node);
} }
return dijkstraResult{ return .{ costs, parents };
.costs = costs,
.path = parents,
};
} }
/// finds the cheapest node among the not yet processed ones. /// finds the cheapest node among the not yet processed ones.
fn findCheapestNode(costs: *std.StringHashMap(f32), processed: *std.BufSet) ?[]const u8 { 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 lowest_cost_node: ?[]const u8 = null;
var it = costs.iterator(); var it = costs.iterator();
@@ -123,39 +123,35 @@ fn findCheapestNode(costs: *std.StringHashMap(f32), processed: *std.BufSet) ?[]c
} }
test "dijkstra" { test "dijkstra" {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(std.testing.allocator);
var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit();
defer { const alloc = arena.allocator();
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 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("a", 6);
try start.put("b", 2); try start.put("b", 2);
try graph.put("start", &start); 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 a.put("finish", 1);
try graph.put("a", &a); 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("a", 3);
try b.put("finish", 5); try b.put("finish", 5);
try graph.put("b", &b); 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); 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(costs.get("a").?, 5);
try std.testing.expectEqual(result.costs.get("b").?, 2); try std.testing.expectEqual(costs.get("b").?, 2);
try std.testing.expectEqual(result.costs.get("finish").?, 6); try std.testing.expectEqual(costs.get("finish").?, 6);
try std.testing.expectEqual(result.path.get("b").?, "start"); try std.testing.expectEqual(path.get("b").?, "start");
try std.testing.expectEqual(result.path.get("a").?, "b"); try std.testing.expectEqual(path.get("a").?, "b");
try std.testing.expectEqual(result.path.get("finish").?, "a"); try std.testing.expectEqual(path.get("finish").?, "a");
} }

View File

@@ -7,43 +7,45 @@ pub fn main() !void {
var arena = heap.ArenaAllocator.init(gpa.allocator()); var arena = heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); defer arena.deinit();
var states_needed_array = [_][]const u8{ "mt", "wa", "or", "id", "nv", "ut", "ca", "az" }; const ally = arena.allocator();
var states_needed = std.BufSet.init(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| { for (states_needed_array) |sn| {
try states_needed.insert(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()); var k_one = std.BufSet.init(ally);
try kone.insert("id"); try k_one.insert("id");
try kone.insert("nv"); try k_one.insert("nv");
try kone.insert("ut"); try k_one.insert("ut");
try stations.put("kone", &kone);
var ktwo = std.BufSet.init(arena.allocator()); var k_two = std.BufSet.init(ally);
try ktwo.insert("wa"); try k_two.insert("wa");
try ktwo.insert("id"); try k_two.insert("id");
try ktwo.insert("mt"); try k_two.insert("mt");
try stations.put("ktwo", &ktwo);
var kthree = std.BufSet.init(arena.allocator()); var k_three = std.BufSet.init(ally);
try kthree.insert("or"); try k_three.insert("or");
try kthree.insert("nv"); try k_three.insert("nv");
try kthree.insert("ca"); try k_three.insert("ca");
try stations.put("kthree", &kthree);
var kfour = std.BufSet.init(arena.allocator()); var k_four = std.BufSet.init(ally);
try kfour.insert("nv"); try k_four.insert("nv");
try kfour.insert("ut"); try k_four.insert("ut");
try stations.put("kfour", &kfour);
var kfive = std.BufSet.init(arena.allocator()); var k_five = std.BufSet.init(ally);
try kfive.insert("ca"); try k_five.insert("ca");
try kfive.insert("az"); try k_five.insert("az");
try stations.put("kfive", &kfive);
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| { for (stations_covering) |sc| {
std.debug.print("{s}\n", .{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(); var it = stations.iterator();
while (it.next()) |station| { while (it.next()) |station| {
var covered = &std.ArrayList([]const u8).init(allocator); var covered = std.ArrayList([]const u8).init(allocator);
try intersect(states_needed, station.value_ptr.*, covered); try intersect(states_needed, station.value_ptr.*, &covered);
if (covered.items.len > states_covered.len) { if (covered.items.len > states_covered.len) {
best_station = station.key_ptr.*; best_station = station.key_ptr.*;
states_covered = covered.items; states_covered = covered.items;
@@ -80,10 +82,61 @@ fn setCovering(allocator: mem.Allocator, stations: *std.StringHashMap(*std.BufSe
return final_array.toOwnedSlice(); 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 { fn intersect(left: *std.BufSet, right: *std.BufSet, intersection: *std.ArrayList([]const u8)) !void {
var l_it = left.iterator(); var l_it = left.iterator();
var r_it = right.iterator();
while (l_it.next()) |l| { while (l_it.next()) |l| {
var r_it = right.iterator();
while (r_it.next()) |r| { while (r_it.next()) |r| {
if (std.mem.eql(u8, l.*, r.*)) { if (std.mem.eql(u8, l.*, r.*)) {
try intersection.append(l.*); 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 { fn difference(lessening: *std.BufSet, subtracting: [][]const u8) void {
var less_it = lessening.iterator(); var less_it = lessening.iterator();
@@ -104,55 +205,52 @@ fn difference(lessening: *std.BufSet, subtracting: [][]const u8) void {
} }
} }
test "setCovering" { test "difference" {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(std.testing.allocator);
var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit();
defer { const ally = arena.allocator();
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 // 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 sub = [_][]const u8{ "avocado", "kiwi", "ananas" };
var states_needed = std.BufSet.init(arena.allocator());
for (states_needed_array) |sn| { difference(&less, &sub);
try states_needed.insert(sn);
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()); difference(&less, &sub);
try kone.insert("id");
try kone.insert("nv");
try kone.insert("ut");
try stations.put("kone", &kone);
var ktwo = std.BufSet.init(arena.allocator()); try std.testing.expect(less.count() == 0);
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);
} }
} }

View File

@@ -2,18 +2,20 @@ const std = @import("std");
const heap = std.heap; const heap = std.heap;
const math = std.math; const math = std.math;
const expect = std.testing.expect; const expect = std.testing.expect;
const expectEqualStrings = std.testing.expectEqualStrings;
pub fn main() !void { pub fn main() !void {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var gpa = heap.GeneralPurposeAllocator(.{}){};
var arena = heap.ArenaAllocator.init(gpa.allocator()); var arena = heap.ArenaAllocator.init(gpa.allocator());
defer arena.deinit(); defer arena.deinit();
var n = try subsequence(arena.allocator(), "fish", "fosh"); const n, const sub = try subsequence(arena.allocator(), "fish", "fosh");
std.debug.print("{d}\n", .{n}); 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 grid = try allocator.alloc([]u32, a.len + 1);
var subseq = try std.ArrayList(u8).initCapacity(allocator, @max(a.len, b.len));
for (grid) |*row| { for (grid) |*row| {
row.* = try allocator.alloc(u32, b.len + 1); 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) { while (j <= b.len) : (j += 1) {
if (a[i - 1] == b[j - 1]) { if (a[i - 1] == b[j - 1]) {
grid[i][j] = grid[i - 1][j - 1] + 1; grid[i][j] = grid[i - 1][j - 1] + 1;
try subseq.append(a[i - 1]);
} else { } 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" { test "subsequence" {
var tests = [_]struct { const tests = [_]struct {
a: []const u8, a: []const u8,
b: []const u8, b: []const u8,
exp: u32, expected: struct { u32, []const u8 },
}{ }{
.{ .a = "abc", .b = "abcd", .exp = 3 }, .{ .a = "abc", .b = "abcd", .expected = .{ 3, "abc" } },
.{ .a = "pera", .b = "mela", .exp = 2 }, .{ .a = "pera", .b = "mela", .expected = .{ 2, "ea" } },
.{ .a = "banana", .b = "kiwi", .exp = 0 }, .{ .a = "banana", .b = "kiwi", .expected = .{ 0, "" } },
}; };
for (tests) |t| { for (tests) |t| {
var gpa = heap.GeneralPurposeAllocator(.{}){}; var arena = heap.ArenaAllocator.init(std.testing.allocator);
var arena = heap.ArenaAllocator.init(gpa.allocator()); defer arena.deinit();
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 n = try subsequence(arena.allocator(), t.a, t.b); const actual = try subsequence(arena.allocator(), t.a, t.b);
try expect(n == t.exp);
try std.testing.expectEqualDeep(t.expected, actual);
} }
} }