diff --git a/README.md b/README.md index 8b13789..604b8ce 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ +# Advent of code +My participation to advent of code 2024. + +Did it in zig, trying to be as memory efficient and fast as possible. + +## Benchmark + +Can be run with `zig run -O ReleaseFast benchmark.zig` + +| Day | Part | Mean (μs) | Min (μs) | Max (μs) | +|-----|------|-------------------|----------|----------| +| 1 | 1 | +29 ± 3.00 | +28 | +78 | +| 1 | 2 | +24 ± 2.65 | +24 | +56 | +|-----|------|-------------------|----------|----------| +| 2 | 1 | +43 ± 8.37 | +37 | +241 | +| 2 | 2 | +328 ± 33.84 | +298 | +728 | +|-----|------|-------------------|----------|----------| +| 3 | 1 | +24 ± 6.24 | +20 | +170 | +| 3 | 2 | +23 ± 1.73 | +20 | +58 | +|-----|------|-------------------|----------|----------| +| 4 | 2 | +229 ± 26.15 | +214 | +417 | +| 4 | 2 | +232 ± 26.13 | +213 | +337 | +|-----|------|-------------------|----------|----------| +| Total | +932 ± 108.11 | +854 | +2085 | diff --git a/benchmark.zig b/benchmark.zig new file mode 100644 index 0000000..5fd61a2 --- /dev/null +++ b/benchmark.zig @@ -0,0 +1,98 @@ +const std = @import("std"); +const print = std.debug.print; + +const d11 = @import("day1/part1.zig"); +const d12 = @import("day1/part2.zig"); +const d21 = @import("day2/part1.zig"); +const d22 = @import("day2/part2.zig"); +const d31 = @import("day3/part1.zig"); +const d32 = @import("day3/part2.zig"); +const d41 = @import("day4/part1.zig"); +const d42 = @import("day4/part2.zig"); + +const NUMBER_OF_RUN = 1000; + +var total_mean: i64 = 0; +var total_min: i64 = 0; +var total_max: i64 = 0; +var total_std_dev: f64 = 0; + +pub fn main() !void { + separator(); + print("| Day | Part | Mean (μs) | Min (μs) | Max (μs) |\n", .{}); + separator(); + try benchmark(d11.main, 1, 1); + try benchmark(d12.main, 1, 2); + separator(); + try benchmark(d21.main, 2, 1); + try benchmark(d22.main, 2, 2); + separator(); + try benchmark(d31.main, 3, 1); + try benchmark(d32.main, 3, 2); + separator(); + try benchmark(d42.main, 4, 2); + try benchmark(d42.main, 4, 2); + separator(); + print("| Total | {d: >8} ± {d: <6.2} | {d:>8} | {d:>8} |\n", .{ total_mean, total_std_dev, total_min, total_max }); + separator(); +} + +pub fn benchmark(func: anytype, day: u8, part: u8) !void { + var time_buff: [NUMBER_OF_RUN]i64 = undefined; + + for (0..NUMBER_OF_RUN) |i| { + const time_start = std.time.microTimestamp(); + try func(); + time_buff[i] = std.time.microTimestamp() - time_start; + } + + // Adjusted tabs for better alignment + print("| {d:<3} | {d:<4} | {d:>8} ± {d:<6.2} | {d:>8} | {d:>8} |\n", .{ day, part, mean(time_buff), std_dev(time_buff), min(time_buff), max(time_buff) }); + total_mean += mean(time_buff); + total_min += min(time_buff); + total_max += max(time_buff); + total_std_dev += std_dev(time_buff); +} + +pub fn separator() void { + print("|-----|------|-------------------|----------|----------|\n", .{}); +} + +fn min(array: [NUMBER_OF_RUN]i64) i64 { + var current_min: i64 = 999999999999; + for (array) |value| { + if (value < current_min) current_min = value; + } + return current_min; +} + +fn max(array: [NUMBER_OF_RUN]i64) i64 { + var current_max: i64 = 0; + for (array) |value| { + if (value > current_max) current_max = value; + } + return current_max; +} + +fn mean(array: [NUMBER_OF_RUN]i64) i64 { + var total: i64 = 0; + for (array) |value| { + total += value; + } + return @divFloor(total, NUMBER_OF_RUN); +} + +fn variance(array: [NUMBER_OF_RUN]i64) i64 { + const m = mean(array); + var square_diff: i64 = 0; + for (array) |value| { + square_diff += (value - m) * (value - m); + } + return @divFloor(square_diff, NUMBER_OF_RUN); +} + +fn std_dev(array: [NUMBER_OF_RUN]i64) f64 { + const vari = @as(f64, @floatFromInt(variance(array))); + + return @sqrt(vari); +} diff --git a/day1/main.zig b/day1/part1.zig similarity index 76% rename from day1/main.zig rename to day1/part1.zig index 75e1592..5cf2cd6 100644 --- a/day1/main.zig +++ b/day1/part1.zig @@ -4,7 +4,6 @@ const print = std.debug.print; var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB pub fn main() !void { - const time_start = std.time.microTimestamp(); var left: [1000]i32 = undefined; var right: [1000]i32 = undefined; @@ -24,16 +23,7 @@ pub fn main() !void { std.mem.sort(i32, &left, {}, comptime std.sort.asc(i32)); std.mem.sort(i32, &right, {}, comptime std.sort.asc(i32)); - const time_parsing = std.time.microTimestamp(); - print("Parsing time: {d}μs\n", .{time_parsing - time_start}); - - const total_distance = distance(left, right); - const total_similarity = try similarity(left, right); - - const time_end = std.time.microTimestamp(); - print("Compute time: {d}μs\n", .{time_end - time_parsing}); - print("\tDistance: {d}\n", .{total_distance}); - print("\tSimilarity: {d}\n", .{total_similarity}); + try std.testing.expectEqual(1110981, distance(left, right)); } fn distance(left: [1000]i32, right: [1000]i32) u32 { diff --git a/day1/part2.zig b/day1/part2.zig new file mode 100644 index 0000000..4519f1a --- /dev/null +++ b/day1/part2.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const print = std.debug.print; + +var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB + +pub fn main() !void { + var left: [1000]i32 = undefined; + var right: [1000]i32 = undefined; + + const file = try std.fs.cwd().openFile("day1/input", .{}); + defer file.close(); + const len = try file.readAll(&file_buf); + + var i: u32 = 0; + var iter = std.mem.split(u8, file_buf[0..len], "\n"); + while (iter.next()) |line| { + if (i == 1000) continue; + left[i] = try std.fmt.parseInt(i32, line[0..5], 10); + right[i] = try std.fmt.parseInt(i32, line[8..13], 10); + i += 1; + } + + try std.testing.expectEqual(24869388, similarity(left, right)); +} + +fn distance(left: [1000]i32, right: [1000]i32) u32 { + var total_distance: u32 = 0; + for (left, right) |l, r| { + total_distance += @abs(r - l); + } + return total_distance; +} + +fn similarity(left: [1000]i32, right: [1000]i32) !i32 { + // Make a map with value -> occurence + var buff: [1024 * 100]u8 = undefined; + var fbuf = std.heap.FixedBufferAllocator.init(&buff); + const allocator = fbuf.allocator(); + + var map = std.AutoHashMap(i32, u8).init(allocator); + defer map.deinit(); + + for (right) |r| { + if (map.get(r)) |r_count| { + try map.put(r, r_count + 1); + } else { + try map.put(r, 1); + } + } + + var total_similarity: i32 = 0; + for (left) |l| { + if (map.get(l)) |r_count| total_similarity += l * r_count; + } + return total_similarity; +} diff --git a/day2/part1.zig b/day2/part1.zig new file mode 100644 index 0000000..7caa0c7 --- /dev/null +++ b/day2/part1.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const print = std.debug.print; + +const State = enum { First, Second, Incr, Decr }; // Increase Decrease + +var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB +var second_buf: [64]u8 = undefined; + +pub fn main() !void { + const file = try std.fs.cwd().openFile("day2/input", .{}); + defer file.close(); + const len = try file.readAll(&file_buf); + + var total_safe: usize = 0; + var iter = std.mem.split(u8, file_buf[0..len], "\n"); + while (iter.next()) |line| { + if (std.mem.eql(u8, line, "")) continue; + + if (try isSafe(line)) { + total_safe += 1; + continue; + } + } + + try std.testing.expectEqual(564, total_safe); +} + +fn isSafe(line: []const u8) !bool { + var state: State = .First; + var previous: u8 = 0; + var current: u8 = 0; + + var it = std.mem.split(u8, line, " "); + while (it.next()) |x| { + defer previous = current; + current = try std.fmt.parseInt(u8, x, 10); + + if (state != .First and previous == current) return false; + if (state != .First and ((previous > current and (previous - current) > 3) or (previous < current and (current - previous) > 3))) return false; + + state = switch (state) { + .First => .Second, + .Second => if (previous > current) .Decr else .Incr, + .Decr => if (previous > current) .Decr else return false, + .Incr => if (previous < current) .Incr else return false, + }; + } + + return true; +} + +test "Is safe" { + try std.testing.expect(try isSafe("1 2 3")); + try std.testing.expect(try isSafe("1 2 3 4 5 6 7 8 9")); + try std.testing.expect(try isSafe("243 241 239")); + try std.testing.expect(!try isSafe("1 2 3 8")); + try std.testing.expect(!try isSafe("1 2 3 2 1")); + try std.testing.expect(!try isSafe("1 2 3 3 1")); + try std.testing.expect(!try isSafe("1 1 3 3 1")); +} diff --git a/day2/main.zig b/day2/part2.zig similarity index 68% rename from day2/main.zig rename to day2/part2.zig index 5e34a08..16c3d57 100644 --- a/day2/main.zig +++ b/day2/part2.zig @@ -3,57 +3,43 @@ const print = std.debug.print; const State = enum { First, Second, Incr, Decr }; // Increase Decrease -var line_buf: [64]u8 = undefined; +var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB var second_buf: [64]u8 = undefined; -var previous: u8 = 0; -var current: u8 = 0; pub fn main() !void { - const time_start = std.time.microTimestamp(); - - var fbuf = std.heap.FixedBufferAllocator.init(&line_buf); - const alloc = fbuf.allocator(); - var line = std.ArrayList(u8).initCapacity(alloc, line_buf.len) catch unreachable; - const file = try std.fs.cwd().openFile("day2/input", .{}); defer file.close(); + const len = try file.readAll(&file_buf); - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); - - const writer = line.writer(); var total_safe: usize = 0; - while (reader.streamUntilDelimiter(writer, '\n', null)) { - // Clear the line so we can reuse it. - defer line.clearRetainingCapacity(); + var iter = std.mem.split(u8, file_buf[0..len], "\n"); + while (iter.next()) |line| { + if (std.mem.eql(u8, line, "")) continue; - if (try isSafe(line.items)) { + if (try isSafe(line)) { total_safe += 1; continue; } var i: u8 = 0; - var it = std.mem.split(u8, line.items, " "); + var it = std.mem.split(u8, line, " "); while (it.next()) |_| { - const new_line = try removeOneIndex(line.items, i); + const new_line = try removeOneIndex(line, i); if (try isSafe(new_line)) { total_safe += 1; break; } i += 1; } - } else |err| switch (err) { - error.EndOfStream => {}, - else => return err, // Propagate error } - const time_end = std.time.microTimestamp(); - print("Total time: {d}μs\n", .{time_end - time_start}); - print("Total safe: {d}\n", .{total_safe}); + try std.testing.expectEqual(604, total_safe); } fn isSafe(line: []const u8) !bool { var state: State = .First; + var previous: u8 = 0; + var current: u8 = 0; var it = std.mem.split(u8, line, " "); while (it.next()) |x| { diff --git a/day3/part1.zig b/day3/part1.zig new file mode 100644 index 0000000..50c8036 --- /dev/null +++ b/day3/part1.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const print = std.debug.print; + +const LookingFor = enum { + m, + u, + l, + l_brace, + X, + Y, +}; + +var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB + +pub fn main() !void { + const file = try std.fs.cwd().openFile("day3/input", .{}); + defer file.close(); + const len = try file.readAll(&file_buf); + + try std.testing.expectEqual(190604937, try parse(file_buf[0..len])); +} + +fn parse(input: []const u8) !u32 { + var state: LookingFor = .m; + var total: u32 = 0; + var intX_len: u8 = 0; + var intY_len: u8 = 0; + for (input, 0..) |c, i| switch (state) { + .m => switch (c) { + 'm' => state = .u, + else => continue, + }, + .u => switch (c) { + 'u' => state = .l, + else => state = .m, + }, + .l => switch (c) { + 'l' => state = .l_brace, + else => state = .m, + }, + .l_brace => switch (c) { + '(' => { + state = .X; + intX_len = 0; + intY_len = 0; + }, + else => state = .m, + }, + .X => switch (c) { + ',' => state = if (intX_len > 0) .Y else .m, + '0'...'9' => if (intX_len == 3) { + state = .m; + } else { + intX_len += 1; + }, + else => state = .m, + }, + .Y => switch (c) { + ')' => { + state = .m; + if (intY_len > 0) { + const x = try std.fmt.parseInt(u32, input[i - (intX_len + intY_len + 1) .. i - intY_len - 1], 10); + const y = try std.fmt.parseInt(u32, input[i - intY_len .. i], 10); + total += x * y; + } + }, + '0'...'9' => { + if (intY_len == 3) { + state = .m; + continue; + } + intY_len += 1; + }, + else => state = .m, + }, + }; + return total; +} diff --git a/day3/main.zig b/day3/part2.zig similarity index 88% rename from day3/main.zig rename to day3/part2.zig index 2e6f158..4203015 100644 --- a/day3/main.zig +++ b/day3/part2.zig @@ -19,27 +19,20 @@ const LookingFor = enum { var file_buf: [1024 * 1024]u8 = undefined; // The file do 20kB, I give it 1MB pub fn main() !void { - const time_start = std.time.microTimestamp(); - const file = try std.fs.cwd().openFile("day3/input", .{}); defer file.close(); const len = try file.readAll(&file_buf); - var total: u32 = 0; - var enable = true; - total += try parse(file_buf[0..len], &enable); - - const time_end = std.time.microTimestamp(); - print("Total time: {d}μs\n", .{time_end - time_start}); - print("Total safe: {d}\n", .{total}); + try std.testing.expectEqual(82857512, try parse(file_buf[0..len])); } -fn parse(input: []const u8, enable: *bool) !u32 { +fn parse(input: []const u8) !u32 { var state: LookingFor = .m_or_d; var total: u32 = 0; var intX_len: u8 = 0; var intY_len: u8 = 0; + var enable = true; var enable_buff: bool = true; for (input, 0..) |c, i| switch (state) { .m_or_d => switch (c) { @@ -89,7 +82,7 @@ fn parse(input: []const u8, enable: *bool) !u32 { }, .r_brace_do => switch (c) { ')' => { - enable.* = enable_buff; + enable = enable_buff; enable_buff = true; state = .m_or_d; }, @@ -108,7 +101,7 @@ fn parse(input: []const u8, enable: *bool) !u32 { ')' => { state = .m_or_d; if (intY_len > 0) { - if (!enable.*) continue; + if (!enable) continue; const x = try std.fmt.parseInt(u32, input[i - (intX_len + intY_len + 1) .. i - intY_len - 1], 10); const y = try std.fmt.parseInt(u32, input[i - intY_len .. i], 10); total += x * y; diff --git a/day4/part1.zig b/day4/part1.zig index efc89cb..4c06825 100644 --- a/day4/part1.zig +++ b/day4/part1.zig @@ -55,15 +55,9 @@ const masks = [_][4][4]u8{ }; pub fn main() !void { - const time_start = std.time.microTimestamp(); - setMatrice('.'); try fillMatrice(); - const total = countMask(); - - const time_end = std.time.microTimestamp(); - print("Total time: {d}μs\n", .{time_end - time_start}); - print("Total: {d}\n", .{total}); + try std.testing.expectEqual(2401, countMask()); } fn evaluate(mask: [4][4]u8, sub: [4][4]u8) bool { diff --git a/day4/part2.zig b/day4/part2.zig index 357e122..3e08fb5 100644 --- a/day4/part2.zig +++ b/day4/part2.zig @@ -27,15 +27,9 @@ const masks = [_][3][3]u8{ }; pub fn main() !void { - const time_start = std.time.microTimestamp(); - setMatrice('.'); try fillMatrice(); - const total = countMask(); - - const time_end = std.time.microTimestamp(); - print("Total time: {d}μs\n", .{time_end - time_start}); - print("Total: {d}\n", .{total}); + try std.testing.expectEqual(1822, countMask()); } fn evaluate(mask: [3][3]u8, sub: [3][3]u8) bool {