From f080dd64dd6a75d042eedf75b75834382c1502a4 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sat, 7 Dec 2024 23:27:28 +0100 Subject: [PATCH] day 7 finished --- day1.zig | 32 +- day2.zig | 64 ++- day3.zig | 18 +- day4.zig | 18 +- day5.zig | 18 +- day6.zig | 18 +- day7.zig | 328 ++++++++++++++ inputs/day7.txt | 850 +++++++++++++++++++++++++++++++++++++ lib/spice.zig | 545 ++++++++++++++++++++++++ utils.zig => lib/utils.zig | 128 +++--- 10 files changed, 1866 insertions(+), 153 deletions(-) create mode 100644 day7.zig create mode 100644 inputs/day7.txt create mode 100644 lib/spice.zig rename utils.zig => lib/utils.zig (86%) diff --git a/day1.zig b/day1.zig index 9c92c01..8888312 100644 --- a/day1.zig +++ b/day1.zig @@ -1,27 +1,13 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); + +const filename = "inputs/day1.txt"; pub fn main() !void { - // // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) - // std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); - - // // stdout is for the actual output of your application, for example if you - // // are implementing gzip, then only the compressed bytes should be sent to - // // stdout, not any debugging messages. - // const stdout_file = std.io.getStdOut().writer(); - // var bw = std.io.bufferedWriter(stdout_file); - // const stdout = bw.writer(); - - // try stdout.print("Run `zig build test` to run the tests.\n", .{}); - - // try bw.flush(); // don't forget to flush! - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const filename = "inputs/day1.txt"; - { const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); @@ -158,20 +144,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 11); + try std.testing.expectEqual(11, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day1.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 1506483); + try std.testing.expectEqual(1506483, result); } test "part2 example" { @@ -190,18 +174,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 31); + try std.testing.expectEqual(31, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day1.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 23126924); + try std.testing.expectEqual(23126924, result); } diff --git a/day2.zig b/day2.zig index 98c7080..bef5bfc 100644 --- a/day2.zig +++ b/day2.zig @@ -1,6 +1,31 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); + +const filename = "inputs/day2.txt"; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = gpa.allocator(); + + { + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part1(alloc, file_reader.reader()); + + try std.io.getStdOut().writer().print("Day 2, part 1: {}\n", .{result}); + } + + { + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part2(alloc, file_reader.reader()); + + try std.io.getStdOut().writer().print("Day 2, part 2: {}\n", .{result}); + } +} fn Chain(comptime T: type) type { return struct { @@ -29,31 +54,6 @@ fn Chain(comptime T: type) type { }; } -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); - - const filename = "inputs/day2.txt"; - - { - const file_reader = try utils.FileReader.init(alloc, filename); - defer file_reader.deinit(); - - const result = try part1(alloc, file_reader.reader()); - - try std.io.getStdOut().writer().print("Day 2, part 1: {}\n", .{result}); - } - - { - const file_reader = try utils.FileReader.init(alloc, filename); - defer file_reader.deinit(); - - const result = try part2(alloc, file_reader.reader()); - - try std.io.getStdOut().writer().print("Day 2, part 2: {}\n", .{result}); - } -} - fn check(report: []const i32) bool { var window = std.mem.window(i32, report, 2, 1); @@ -191,20 +191,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 2); + try std.testing.expectEqual(2, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day2.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 549); + try std.testing.expectEqual(549, result); } test "part2 example" { @@ -223,18 +221,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 4); + try std.testing.expectEqual(4, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day2.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 589); + try std.testing.expectEqual(589, result); } diff --git a/day3.zig b/day3.zig index c0c5b1b..32d1ed7 100644 --- a/day3.zig +++ b/day3.zig @@ -1,15 +1,15 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); const isDigit = std.ascii.isDigit; +const filename = "inputs/day3.txt"; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const filename = "inputs/day3.txt"; - { const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); @@ -240,20 +240,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 161); + try std.testing.expectEqual(161, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day3.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 156388521); + try std.testing.expectEqual(156388521, result); } test "part2 example" { @@ -265,18 +263,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 48); + try std.testing.expectEqual(48, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day3.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 75920122); + try std.testing.expectEqual(75920122, result); } diff --git a/day4.zig b/day4.zig index 98a4561..504b0a3 100644 --- a/day4.zig +++ b/day4.zig @@ -1,13 +1,13 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); + +const filename = "inputs/day4.txt"; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const filename = "inputs/day4.txt"; - { const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); @@ -183,20 +183,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 18); + try std.testing.expectEqual(18, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day4.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 2593); + try std.testing.expectEqual(2593, result); } test "part2 example" { @@ -219,18 +217,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 9); + try std.testing.expectEqual(9, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day4.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 1950); + try std.testing.expectEqual(1950, result); } diff --git a/day5.zig b/day5.zig index dfb18dc..dbb14c7 100644 --- a/day5.zig +++ b/day5.zig @@ -1,15 +1,15 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); const List = std.DoublyLinkedList(u8); +const filename = "inputs/day5.txt"; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const filename = "inputs/day5.txt"; - { const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); @@ -381,20 +381,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 143); + try std.testing.expectEqual(143, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day5.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 5129); + try std.testing.expectEqual(5129, result); } test "part2 example" { @@ -435,18 +433,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 123); + try std.testing.expectEqual(123, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day5.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 4077); + try std.testing.expectEqual(4077, result); } diff --git a/day6.zig b/day6.zig index 92e5212..bda28e2 100644 --- a/day6.zig +++ b/day6.zig @@ -1,13 +1,13 @@ const std = @import("std"); -const utils = @import("utils.zig"); +const utils = @import("lib/utils.zig"); + +const filename = "inputs/day6.txt"; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const filename = "inputs/day6.txt"; - { const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); @@ -403,20 +403,18 @@ test "part1 example" { const result = try part1(alloc, stream.reader()); - try std.testing.expect(result == 41); + try std.testing.expectEqual(41, result); } test "part1 input" { const alloc = std.testing.allocator; - const filename = "inputs/day6.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part1(alloc, file_reader.reader()); - try std.testing.expect(result == 4722); + try std.testing.expectEqual(4722, result); } test "part2 example" { @@ -439,18 +437,16 @@ test "part2 example" { const result = try part2(alloc, stream.reader()); - try std.testing.expect(result == 6); + try std.testing.expectEqual(6, result); } test "part2 input" { const alloc = std.testing.allocator; - const filename = "inputs/day6.txt"; - const file_reader = try utils.FileReader.init(alloc, filename); defer file_reader.deinit(); const result = try part2(alloc, file_reader.reader()); - try std.testing.expect(result == 1602); + try std.testing.expectEqual(1602, result); } diff --git a/day7.zig b/day7.zig new file mode 100644 index 0000000..30fb48f --- /dev/null +++ b/day7.zig @@ -0,0 +1,328 @@ +const std = @import("std"); + +const utils = @import("lib/utils.zig"); + +const spice = @import("lib/spice.zig"); + +const filename = "inputs/day7.txt"; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = gpa.allocator(); + + { + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part1(alloc, file_reader.reader()); + + try std.io.getStdOut().writer().print("Day 7, part 1: {}\n", .{result}); + } + + { + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part2(alloc, file_reader.reader()); + + try std.io.getStdOut().writer().print("Day 7, part 1: {}\n", .{result}); + } +} + +fn checkEqnPlusMul(test_value: u64, cur_value: u64, eqn: []const u64) bool { + if (eqn.len == 0) { + return cur_value == test_value; + } + + const x = eqn[0]; + + // + + { + const next_value = cur_value + x; + + // if next_value > test_value: abort, can never get smaller + if (next_value <= test_value) { + if (checkEqnPlusMul(test_value, next_value, eqn[1..])) { + return true; + } + } + } + + // * + { + const next_value = cur_value * x; + + // if next_value > test_value: abort, can never get smaller + if (next_value <= test_value) { + if (checkEqnPlusMul(test_value, next_value, eqn[1..])) { + return true; + } + } + } + + return false; +} + +fn checkEqnPlusMulPar( + t: *spice.Task, + args: struct { + test_value: u64, + cur_value: u64, + eqn: []const u64, + }, +) bool { + const test_value = args.test_value; + const cur_value = args.cur_value; + const eqn = args.eqn; + + if (cur_value > test_value) { + return false; + } + + if (eqn.len == 0) { + return cur_value == test_value; + } + + const x = eqn[0]; + + var plus_fut = spice.Future(@TypeOf(args), bool).init(); + + // + + { + const next_value = cur_value + x; + + // if next_value > test_value: abort, can never get smaller + plus_fut.fork(t, checkEqnPlusMulPar, .{ + .test_value = test_value, + .cur_value = next_value, + .eqn = eqn[1..], + }); + } + + // * + { + const next_value = cur_value * x; + + // if next_value > test_value: abort, can never get smaller + if (next_value <= test_value) { + if (t.call(bool, checkEqnPlusMulPar, .{ .test_value = test_value, .cur_value = next_value, .eqn = eqn[1..] })) { + _ = plus_fut.join(t); + return true; + } + } + } + + if (plus_fut.join(t)) |b| { + return b; + } else { + const next_value = cur_value + x; + + return t.call(bool, checkEqnPlusMulPar, .{ .test_value = test_value, .cur_value = next_value, .eqn = eqn[1..] }); + } +} + +fn checkEqnPlusMulConcat(test_value: u64, cur_value: u64, eqn: []u64) bool { + if (eqn.len == 0) { + return cur_value == test_value; + } + + const x = eqn[0]; + + // + + { + const next_value = cur_value + x; + + // if next_value > test_value: abort, can never get smaller + if (next_value <= test_value) { + if (checkEqnPlusMulConcat(test_value, next_value, eqn[1..])) { + return true; + } + } + } + + if (cur_value == 0) { + // for first value only plus is posible + return false; + } + + // * + { + const next_value = cur_value * x; + + // if next_value > test_value: abort, can never get smaller + if (next_value <= test_value) { + if (checkEqnPlusMulConcat(test_value, next_value, eqn[1..])) { + return true; + } + } + } + + // || + { + const x_num_digits = utils.num_digits(x); + + const shift = std.math.powi(u64, 10, x_num_digits) catch unreachable; + + const new_value = shift * cur_value + x; + + // check with concatted value + + if (checkEqnPlusMulConcat(test_value, new_value, eqn[1..])) { + return true; + } + } + + return false; +} + +fn part1(alloc: std.mem.Allocator, reader: anytype) !u64 { + var sum_calibration: u64 = 0; + + // var thread_pool = spice.ThreadPool.init(alloc); + // defer thread_pool.deinit(); + + // thread_pool.start(.{}); + + var eqn = std.ArrayList(u64).init(alloc); + defer eqn.deinit(); + + var line_reader = utils.lineReader(alloc, reader); + defer line_reader.deinit(); + + while (try line_reader.next()) |line| { + const colon_idx = std.mem.indexOfScalar(u8, line, ':') orelse unreachable; + + const test_value = try std.fmt.parseUnsigned(u64, line[0..colon_idx], 10); + + eqn.clearRetainingCapacity(); + + var number_parser = utils.numberParser(u64, line[colon_idx + 1 ..]); + + while (try number_parser.next()) |n| { + try eqn.append(n); + } + + if (checkEqnPlusMul(test_value, 0, eqn.items)) { + sum_calibration += test_value; + } + + // if (thread_pool.call(bool, checkEqnPlusMulPar, .{ + // .test_value = test_value, + // .cur_value = 0, + // .eqn = eqn.items, + // })) { + // sum_calibration += test_value; + // } + } + + return sum_calibration; +} + +fn part2(alloc: std.mem.Allocator, reader: anytype) !u64 { + var sum_calibration: u64 = 0; + + // var thread_pool = spice.ThreadPool.init(alloc); + // defer thread_pool.deinit(); + + // thread_pool.start(.{}); + + var eqn = std.ArrayList(u64).init(alloc); + defer eqn.deinit(); + + var line_reader = utils.lineReader(alloc, reader); + defer line_reader.deinit(); + + while (try line_reader.next()) |line| { + const colon_idx = std.mem.indexOfScalar(u8, line, ':') orelse unreachable; + + const test_value = try std.fmt.parseUnsigned(u64, line[0..colon_idx], 10); + + eqn.clearRetainingCapacity(); + + var number_parser = utils.numberParser(u64, line[colon_idx + 1 ..]); + + while (try number_parser.next()) |n| { + try eqn.append(n); + } + + if (checkEqnPlusMulConcat(test_value, 0, eqn.items)) { + sum_calibration += test_value; + } + + // if (thread_pool.call(bool, checkEqnPar, .{ + // .test_value = test_value, + // .cur_value = 0, + // .eqn = eqn.items, + // })) { + // sum_calibration += test_value; + // } + } + + return sum_calibration; +} + +test "part1 example" { + const alloc = std.testing.allocator; + + const example = + \\190: 10 19 + \\3267: 81 40 27 + \\83: 17 5 + \\156: 15 6 + \\7290: 6 8 6 15 + \\161011: 16 10 13 + \\192: 17 8 14 + \\21037: 9 7 18 13 + \\292: 11 6 16 20 + ; + + var stream = std.io.fixedBufferStream(example); + + const result = try part1(alloc, stream.reader()); + + try std.testing.expectEqual(3749, result); +} + +test "part1 input" { + const alloc = std.testing.allocator; + + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part1(alloc, file_reader.reader()); + + try std.testing.expectEqual(1985268524462, result); +} + +test "part2 example" { + const alloc = std.testing.allocator; + + const example = + \\190: 10 19 + \\3267: 81 40 27 + \\83: 17 5 + \\156: 15 6 + \\7290: 6 8 6 15 + \\161011: 16 10 13 + \\192: 17 8 14 + \\21037: 9 7 18 13 + \\292: 11 6 16 20 + ; + + var stream = std.io.fixedBufferStream(example); + + const result = try part2(alloc, stream.reader()); + + try std.testing.expectEqual(11387, result); +} + +test "part2 input" { + const alloc = std.testing.allocator; + + const file_reader = try utils.FileReader.init(alloc, filename); + defer file_reader.deinit(); + + const result = try part2(alloc, file_reader.reader()); + + try std.testing.expectEqual(150077710195188, result); +} diff --git a/inputs/day7.txt b/inputs/day7.txt new file mode 100644 index 0000000..ad5791e --- /dev/null +++ b/inputs/day7.txt @@ -0,0 +1,850 @@ +202998336: 686 9 7 62 2 673 +19275222: 361 3 7 170 65 5 223 +23101: 7 694 916 4 6 +2042426: 6 34 2 423 3 +40369523: 8 880 91 45 23 +46629044796: 990 471 4 4 796 +1839056: 3 42 2 4 3 258 703 4 8 +26205: 2 9 5 9 9 4 3 7 44 5 8 7 +2507629: 5 492 8 162 606 +8643264: 5 1 5 7 8 8 1 33 654 1 59 +2379228282: 264 3 5 7 5 7 39 457 2 9 +2088: 8 6 6 7 234 705 2 6 8 14 +28852: 70 7 688 31 36 3 193 +6573214: 8 1 1 140 17 9 62 1 9 1 8 +85235071: 3 4 67 6 17 6 58 7 3 6 4 6 +11017177972: 683 68 3 489 797 4 +7514454: 97 102 5 759 794 319 +1562403920: 9 34 19 61 798 880 +2094: 9 641 896 70 145 333 +26449589: 25 50 2 92 9 592 +150: 37 66 47 +565228: 1 930 13 389 703 3 2 +23863160: 321 4 28 95 662 46 +43766400721: 49 1 97 94 96 699 23 +10539180731: 48 15 1 1 89 7 3 869 9 2 +3677868: 349 510 475 627 9 +943524: 3 968 8 162 2 8 8 3 4 5 8 +5676251693: 6 5 4 7 3 289 2 5 9 6 7 91 +17516: 596 8 29 +3266737179349: 3 4 5 7 7 413 42 5 9 7 7 7 +220516272: 6 7 291 66 51 5 6 278 6 +440606: 72 534 4 2 2 6 2 1 448 3 +10529: 94 589 422 392 2 7 36 +385881484801: 4 4 1 714 52 7 1 7 5 6 8 1 +1756977: 9 48 43 148 3 991 489 +26908592588: 1 6 336 5 8 277 92 588 +11384071922: 78 82 3 2 3 5 57 52 6 2 +5154: 2 44 55 414 9 45 3 93 +1204800178: 16 495 9 10 1 6 6 73 +4028289466: 62 53 52 733 919 6 +855848: 67 456 8 3 3 28 +5577: 52 567 9 4 2 +1256906: 84 54 33 276 2 +1489: 16 9 2 50 1 7 7 7 4 1 8 +274139674: 411 9 2 87 524 324 3 2 +7949: 890 3 2 5 7 2 8 9 663 1 5 +960143: 1 42 2 876 14 3 +661517824: 82 59 36 657 222 4 +3639348: 6 10 655 6 4 6 +100625: 26 1 5 982 44 +89266577886: 7 1 5 119 92 73 7 7 886 +135143059: 30 369 5 7 61 5 2 2 61 +4818: 4 7 332 151 5 9 +241986: 9 320 84 6 60 +199082010: 2 47 831 3 50 98 849 +116986: 556 607 79 9 597 +4593051202: 42 7 333 466 732 7 8 6 +43262715695: 1 5 2 7 7 1 6 674 1 6 6 94 +3672789: 4 3 3 72 787 +66241: 46 80 9 2 1 +6558190292: 3 127 25 975 4 7 8 26 4 +4130: 34 5 104 5 8 61 +60867948960: 9 698 546 73 4 6 90 +25113978462: 7 9 1 889 5 387 40 6 2 +26892: 7 3 320 503 3 9 5 +546749: 719 431 19 9 9 5 8 5 1 8 +20796444564: 28 763 462 7 43 1 83 7 +8249008814046: 6 19 6 931 39 913 67 +8730429939: 8 86 587 39 15 923 +736622586: 9 627 148 9 98 9 +1885860: 9 2 93 72 5 58 60 +1164746124: 25 668 719 392 97 +41946: 771 1 9 43 6 +38992: 261 68 2 97 91 44 +1490: 251 7 3 710 6 +98520531: 82 4 4 30 531 +346525: 3 1 6 5 266 8 1 4 4 95 7 4 +194065650: 508 23 423 36 24 9 9 +136649: 1 1 7 3 8 9 41 9 983 9 3 +1722816: 7 6 41 53 3 286 +15476735: 552 664 7 502 34 4 +979: 112 1 8 83 +8467523846: 9 5 80 139 6 1 32 384 6 +13136: 6 1 25 19 703 6 552 +459: 6 8 37 1 9 +239967904: 7 3 35 8 4 9 8 5 915 6 6 4 +74099204: 6 8 2 43 2 8 49 6 5 204 +34621719552: 90 4 6 4 291 1 40 8 24 +5859557: 731 769 7 437 9 3 554 +208505: 5 5 5 9 7 70 875 7 3 7 1 +3571995333: 5 79 1 3 1 7 9 976 6 6 2 +492132522671: 9 6 9 627 8 9 14 1 9 7 1 +21120: 263 1 1 4 1 20 +1123912968: 3 3 69 5 5 3 856 56 970 +733: 8 85 51 +8626131164: 2 167 805 73 1 508 80 +7560: 1 725 3 2 305 +7897807144: 9 9 927 46 964 +183024: 6 2 9 5 4 9 7 661 1 3 94 6 +62652233: 4 4 8 45 2 114 69 2 3 2 +674752: 84 382 3 7 904 +1422576199: 111 89 96 15 6 14 1 +2653352: 64 78 110 6 52 +403447: 168 1 6 4 7 2 +53254: 44 50 1 53 6 926 3 9 1 1 +151146: 9 9 70 1 148 +9608: 2 54 7 2 6 3 796 2 8 +21517536688: 16 4 5 3 8 572 64 67 +3153752680452: 362 968 9 9 5 8 10 451 +156088: 18 33 3 4 7 1 3 915 5 6 +5377610: 97 20 56 888 1 21 35 +31498040: 9 6 41 40 511 +44: 2 6 30 +371000: 873 982 4 5 10 +578833633: 46 9 68 474 26 +737: 23 459 255 +242901094: 954 279 394 5 91 6 +88101685: 122 9 63 263 6 4 80 85 +11910: 117 70 7 5 128 +987499: 986 79 6 705 +8198251: 513 7 9 9 465 5 5 5 2 1 1 +240310: 184 257 4 54 9 +1836: 81 3 2 2 154 +208356000: 5 2 756 45 53 17 +5915: 8 6 496 9 3 +85269: 52 5 499 93 51 18 +276885499: 2 57 759 10 7 320 59 +237728: 2 9 40 330 50 74 4 +507805531: 49 9 57 115 32 +221498: 2 3 8 23 7 9 1 5 6 9 8 314 +355426819: 1 9 87 127 17 989 1 +2390217: 1 5 11 2 596 772 214 +418797031680: 669 16 64 886 599 2 9 +66941210: 81 77 62 9 754 8 6 7 5 9 +337036: 3 7 6 7 2 876 6 5 91 9 8 4 +2126043264689: 7 13 46 66 8 44 645 4 7 +2907365426: 3 43 7 467 97 66 50 48 +31632440: 12 5 7 12 32 6 49 6 8 70 +57833804: 57 8 33 748 58 +175237918: 8 5 74 759 78 +2783547851: 927 849 300 85 1 +3910: 3 4 34 472 4 +11393205: 2 5 3 9 86 1 200 535 3 +2231811: 22 2 30 1 86 67 4 2 38 +17687226: 6 9 4 6 8 1 3 79 1 2 24 2 +4499404: 3 179 828 4 53 62 870 +123758270900: 35 414 667 7 8 9 98 7 7 +3434132: 38 3 54 7 56 621 35 2 +40501693: 335 314 9 385 22 47 9 +7674373: 8 351 18 917 +24284940297: 562 46 8 789 791 8 9 +115679880304: 1 9 3 14 3 2 345 6 295 8 +218221558806: 65 57 6 180 71 840 7 3 +24591: 408 1 5 751 5 1 21 +30623434865: 306 23 434 838 30 +6294644: 6 1 898 4 767 7 1 146 +2228797860179: 32 277 802 21 19 41 +98285947: 30 2 8 4 626 5 2 5 4 47 +435023: 4 6 87 5 23 +129850: 76 611 21 9 7 +47235677511: 456 243 284 19 79 39 +40216: 73 4 165 1 88 +18603003: 7 9 6 57 7 6 1 7 90 39 3 +872120: 34 53 20 13 98 6 1 +4020580: 67 60 579 1 +182694: 354 86 6 16 6 8 +150106: 3 1 2 47 943 36 8 3 37 1 +686: 7 629 19 32 1 +97081: 3 815 118 82 408 67 +296221: 28 32 24 321 901 +176077987645: 3 27 96 7 3 8 611 764 4 +107532: 45 8 766 2 +55757: 11 86 678 70 549 40 +9039605: 8 9 75 62 27 5 +282175677057: 8 54 7 5 345 55 27 7 57 +9939764518: 3 657 5 5 4 1 6 9 5 90 28 +1939: 14 9 44 2 1 9 +891: 5 2 25 4 67 30 7 87 +5228370: 11 816 945 527 1 2 +1266493205255: 9 7 8 5 4 905 68 7 525 5 +25811913741: 323 7 886 8 9 3 742 +70475: 5 80 5 4 628 5 6 6 599 +29123378297: 8 6 39 416 6 7 4 9 1 2 9 7 +650969606906: 6 8 6 3 7 3 4 8 40 4 6 908 +146078: 9 2 5 885 3 5 3 28 8 8 1 +2317385: 1 8 58 297 2 7 907 +52750080: 6 46 69 6 3 15 964 +159122940: 1 3 3 877 6 6 7 2 3 3 3 10 +4471177: 5 7 47 9 8 5 31 6 3 1 64 9 +46037791744049: 7 3 4 4 7 5 856 7 8 8 5 49 +268: 72 102 94 +24079158: 297 81 1 96 124 56 +4147216379: 1 6 91 54 20 6 1 637 9 +777: 67 6 6 26 6 1 7 +768: 3 6 54 81 5 +129978225: 166 783 13 6 88 +643495198: 957 58 1 672 768 +138381912: 94 9 172 951 +3688: 5 2 5 44 6 5 65 385 63 2 +14645582788: 648 70 3 73 783 4 9 8 7 +157329: 10 81 6 72 3 4 8 59 +7951406334: 5 1 471 65 92 2 3 3 189 +107495: 7 2 8 59 7 1 53 8 2 2 415 +14926586120: 4 799 54 736 597 70 7 +3124665: 3 22 92 25 5 5 789 225 +21191543290: 3 1 4 13 187 4 543 2 90 +83781: 467 133 53 870 55 16 +113: 21 93 2 +44840144656: 8 4 631 355 2 2 5 4 6 5 6 +48334308375: 128 890 628 861 75 5 +28630176326: 168 81 212 8 329 +1320869: 77 1 5 273 59 95 +105265149167: 6 76 837 394 6 7 73 67 +28557568: 911 4 2 75 5 619 +2638: 5 3 3 3 891 2 6 478 97 5 +46350: 9 51 1 86 702 8 3 3 4 8 6 +383184551514: 957 96 4 55 151 4 +5761960680: 55 66 3 73 16 23 99 +368069184: 959 2 5 3 7 63 8 18 2 +154813: 8 7 4 81 3 +381615: 168 74 273 247 3 +206: 26 89 3 8 80 +231063956: 17 32 39 56 424 515 6 +101024: 37 3 3 6 194 4 46 1 8 +1795890: 35 9 5 8 93 +81653384: 3 8 9 1 5 3 203 573 2 6 4 +11547: 4 5 902 8 238 4 55 67 9 +225768477: 225 768 35 8 4 45 +84909: 93 6 6 68 9 +24130695391439: 301 6 33 69 23 929 8 4 +113841727942: 58 9 7 5 8 5 920 6 79 4 1 +3314971539159: 36 4 688 666 4 7 2 478 +595217: 29 9 7 5 692 1 8 9 1 77 7 +28690096: 286 8 5 4 46 68 80 +268249686: 8 79 8 419 89 4 2 84 4 1 +2661776: 1 864 7 3 3 637 75 1 2 +1276948233: 3 9 9 8 37 8 35 5 46 7 6 +35844531: 56 2 6 103 527 6 +1230: 5 88 92 641 57 +56540407: 941 620 3 7 709 6 6 2 6 +8137451: 511 1 49 6 9 2 9 9 5 9 3 7 +116578459: 4 3 66 1 6 7 9 8 57 8 5 6 +2794315200: 465 890 8 844 +141268491349: 2 2 42 351 6 63 349 +2302034228: 7 9 9 8 123 2 6 723 7 1 6 +90320: 52 9 1 7 7 2 1 157 6 5 2 8 +48029662: 79 6 6 296 63 +7338712919: 8 103 363 5 701 7 +1443480: 31 8 5 366 796 8 20 +1015: 8 7 75 875 9 +863938: 4 296 9 81 799 +78309810: 892 81 1 1 804 2 10 +496872015: 8 67 6 34 9 15 480 7 6 +7196630: 702 6 317 5 98 +930917: 849 9 66 6 917 +221828636962: 1 26 60 436 7 1 56 205 +3139541674: 1 1 24 2 50 5 7 6 6 68 4 1 +928052161: 6 7 2 3 77 12 304 80 4 3 +72810234647: 954 3 8 3 4 795 92 555 +36576: 53 98 23 591 9 +927127: 98 192 9 96 49 +489060: 91 9 870 8 7 5 494 +17048360: 434 16 35 1 499 8 70 +384912: 4 1 135 38 713 4 9 6 2 +7850: 5 5 6 39 5 2 7 4 11 2 61 +253774361: 6 4 4 26 5 1 3 1 2 9 5 232 +1411644490: 361 7 8 43 674 571 9 9 +1599457863: 2 2 2 4 713 66 8 39 55 1 +4206768246: 99 6 915 6 15 86 4 842 +1582870: 718 8 701 1 49 720 5 +8806706: 8 806 703 +1412172: 86 422 76 93 26 60 +67578114: 7 8 50 877 602 +3674034: 2 1 5 171 22 471 1 5 39 +576686: 718 802 782 67 1 +554: 66 2 77 301 44 +92371379217: 481 1 64 2 3 11 916 5 7 +50328100: 9 1 699 72 1 100 +1540: 55 526 77 88 794 +2476840: 6 370 6 96 2 6 215 10 4 +19926668: 4 1 7 8 9 6 8 4 2 335 6 7 +2933411: 412 7 49 411 +1573227346: 414 38 273 4 6 +6555363048717: 755 87 1 998 4 8 7 18 +2702927611: 73 370 8 184 761 1 +43118460: 2 66 8 167 9 6 17 396 +48497062: 68 29 7 3 7 77 92 +2405: 4 2 8 15 692 753 +2645592520: 453 73 80 72 520 +671: 27 7 7 560 70 +2679289: 147 25 81 9 214 +1283872: 987 466 21 40 848 +4734270: 983 962 3 9 8 809 +6654: 1 1 8 552 387 7 5 1 1 3 9 +1568488608045: 5 56 508 925 9 8 76 4 6 +70638: 86 82 2 81 38 +14396387239: 65 3 423 650 6 1 7 77 +22329792: 309 54 944 2 8 6 9 7 1 5 +2579656788: 222 29 11 4 5 65 2 88 +851088: 7 2 4 2 8 113 5 9 6 6 168 +94420605041: 55 429 6 4 1 1 512 6 4 3 +67471145910: 5 4 88 838 6 3 3 9 70 9 3 +16791: 1 248 7 54 2 9 8 555 37 +1920: 99 10 27 247 5 2 90 4 +9568569: 9 2 6 3 40 6 44 5 5 24 2 9 +36767: 7 32 940 23 86 +834606: 6 9 7 5 6 76 7 3 2 1 1 82 +17399: 7 80 5 57 8 818 6 2 9 6 8 +35160: 1 4 3 2 66 50 +585688: 8 5 49 8 721 716 +519858640: 78 49 49 2 2 340 +27192883: 716 414 752 17 4 2 4 4 +43200: 4 72 3 5 10 +1740811060: 77 4 9 54 8 6 9 86 685 7 +31026337: 3 912 6 6 5 63 2 4 57 34 +47785521971: 2 7 1 1 3 3 6 971 51 9 7 1 +12417265: 166 9 9 83 8 951 5 +299266: 81 218 266 +92943601032: 1 3 793 301 876 10 3 2 +16349044: 87 87 9 24 4 +262128077: 392 65 660 8 64 807 7 +395845632808: 3 9 6 80 708 8 8 6 4 3 3 1 +766841180: 16 5 28 535 71 412 +20781318: 52 2 89 92 1 47 127 1 +610516424: 76 30 68 5 7 52 23 +165004: 8 55 4 45 6 9 8 9 6 8 5 7 +30785: 8 9 76 48 886 7 4 7 9 +3876026: 9 310 3 7 6 3 448 11 +5362570: 5 1 72 392 38 5 5 +597908736: 79 885 389 96 1 8 552 +751: 437 305 9 +667708: 14 4 1 486 70 7 +353620: 9 4 6 1 736 5 4 5 3 1 8 4 +21857: 25 2 671 31 175 44 +1796: 998 427 57 189 121 4 +45922959: 8 2 8 3 2 82 29 5 9 +74810230: 31 1 89 5 76 69 914 5 +9490: 543 89 3 2 5 +77987894: 33 95 85 4 112 2 6 8 6 1 +4539565074: 45 5 999 153 15 9 4 66 +1420: 772 6 642 +150702760: 835 8 752 49 5 3 2 490 +8624: 53 3 9 15 86 +20348212: 400 956 183 82 76 +7566099: 60 479 360 52 8 7 4 5 4 +3616: 3 1 452 2 +28891452: 47 4 7 6 97 166 51 5 2 +4880483527: 921 241 42 83 527 +111996454926: 51 244 9 45 49 26 +5501454: 1 2 7 3 3 8 8 9 832 4 5 6 +34445161: 5 702 6 6 3 962 5 76 2 9 +7640879724: 374 960 700 59 5 9 4 9 +147568898: 2 4 5 817 6 786 8 9 7 2 +3669375: 68 92 8 6 69 285 +106487172: 7 857 7 9 4 197 2 +3065304: 5 956 7 861 47 4 9 408 +4766: 71 67 9 +9144170: 3 8 9 62 2 7 2 8 1 45 22 1 +839951: 5 1 834 9 53 +53279425: 5 204 9 5 77 32 9 427 +491703729: 8 36 453 28 9 307 7 4 9 +872197928: 87 219 79 26 +2217066984: 510 39 5 905 2 66 67 +2137366: 712 45 3 9 6 +183453566112: 9 98 7 7 76 1 2 6 4 228 +25672: 76 4 40 9 8 +3618384193940: 6 5 2 6 3 2 6 77 193 940 +118692: 61 5 9 80 1 42 +27433643760: 34 7 1 637 53 27 80 +1561862: 86 68 3 89 436 10 +900002589: 275 8 1 48 993 403 18 +43626629648: 28 310 4 94 4 5 865 89 +712428118: 93 93 69 5 8 477 +236: 57 3 4 165 7 +3932199: 8 7 512 512 39 +63295056: 253 3 5 9 744 66 +1689911423: 731 561 1 33 755 7 +84309165951: 1 2 2 820 35 5 3 3 17 8 6 +893906: 7 886 906 +1479002477515: 7 488 5 3 1 5 79 2 7 5 16 +973833: 512 8 840 716 73 +10984: 964 18 1 574 9 4 1 7 1 1 +36944182898: 91 53 766 289 8 +29065149: 5 278 6 1 9 7 514 7 +25994: 24 5 14 90 4 +12914: 7 7 1 9 22 8 8 39 59 937 +4972030: 7 7 7 10 8 11 3 4 210 1 +273: 61 1 203 9 +1127354776: 9 3 3 69 3 9 4 191 5 5 1 +10827018: 488 7 66 34 8 583 6 1 +28851024: 5 1 8 6 1 536 8 75 95 8 +71789911205: 384 744 24 909 70 3 +9855228: 4 819 3 87 75 3 +60066: 592 30 33 80 723 +498922280980: 8 8 9 59 81 3 1 9 355 4 +33479: 87 374 645 245 51 +7535428: 8 93 82 1 74 8 +251418855: 50 40 348 82 979 7 +1562449263: 9 3 77 9 74 579 6 3 1 6 4 +136328861841: 932 57 8 140 611 27 3 +622670265: 6 22 669 423 842 +343372980: 3 4 33 727 5 4 226 +12075: 4 5 5 4 5 5 560 881 8 6 7 +5273: 45 3 8 3 43 6 17 50 900 +354636568946: 36 3 92 4 33 927 6 84 +44385: 19 151 9 2 9 5 71 8 7 1 +449351: 826 544 1 3 4 +519429760: 5 186 8 313 40 +8765956: 8 6 63 84 7 2 4 4 1 464 8 +10725630: 1 968 109 6 5 +5036405220: 122 483 222 1 385 +148523: 3 91 544 4 7 +35013810659: 1 269 376 8 7 5 3 5 7 37 +52456: 4 2 2 83 79 +833001: 201 649 20 49 1 +2171283408: 3 7 721 6 576 4 7 9 548 +4376744812: 166 95 944 7 294 36 +7481335851: 1 8 58 85 6 57 843 +1439812587: 4 9 9 113 94 3 2 9 4 6 87 +60063: 7 48 2 3 7 44 7 3 2 5 17 5 +13923392048: 889 3 3 8 178 41 47 4 2 +112200: 520 65 512 3 3 34 +229593: 1 5 7 8 3 7 1 404 8 1 8 49 +190450250: 283 7 241 545 5 +1065: 35 61 1 9 6 +36085: 1 5 6 4 5 60 2 83 +758640: 758 59 4 43 3 +305235895: 5 70 30 9 646 82 5 92 5 +59688300: 7 822 9 8 300 +513: 2 24 9 3 78 +1518480634835: 14 3 5 5 296 1 6 3 4 835 +458907: 6 15 69 4 8 7 70 8 5 6 7 4 +408368283: 5 986 5 41 78 80 404 +767592280269: 9 14 81 28 418 714 5 9 +67611: 557 5 8 8 5 9 5 3 1 2 4 3 +7253: 8 591 84 9 309 99 6 +413869399: 67 1 1 8 76 2 6 68 2 715 +669060: 2 159 32 77 354 7 +879555: 66 622 1 9 28 1 9 6 5 +144368332: 7 277 398 4 532 99 61 +242361: 61 33 37 185 9 +44185596: 1 85 6 91 941 +610557: 6 10 5 5 7 +8282952: 82 810 82 924 945 +160: 7 9 7 9 5 +1598192649402: 9 9 9 1 1 8 352 4 6 9 402 +4295716554: 737 64 562 9 8 8 554 +2185953: 4 7 4 5 3 312 8 69 1 7 1 5 +5654: 33 28 6 1 1 108 +684624416: 7 978 24 41 6 +994: 17 5 973 +69: 4 33 32 +39140230551: 3 83 3 99 926 230 550 +42835632: 2 2 517 7 2 734 658 8 +346661286: 86 88 2 62 510 34 84 6 +287042: 410 7 42 +226835: 614 3 61 9 6 4 870 85 +5101839: 48 8 2 1 123 3 6 33 36 9 +12050106: 8 896 4 112 5 371 9 3 6 +13072980827: 20 555 636 82 5 +231990528: 9 610 9 5 845 +44838434116: 9 762 92 939 519 15 +226162860: 9 1 62 614 53 2 4 4 3 55 +266400: 55 87 2 7 5 21 5 6 4 2 25 +5427: 35 54 979 3 5 72 +639: 58 77 7 3 213 +1319509681006: 8 9 5 2 65 69 150 1 7 2 3 +9116169: 3 5 5 871 314 43 260 2 +448624: 816 1 6 34 167 1 88 +11470372879: 532 4 3 27 38 8 8 697 7 +222553: 8 9 1 5 403 4 9 5 6 2 9 2 +13363961: 5 93 905 403 5 5 6 2 3 6 +1130380: 3 374 4 428 297 +1638669965109: 7 9 6 9 762 6 23 3 4 3 5 9 +94144023787: 994 12 9 947 74 90 +14050266: 47 550 68 5 542 +56385450: 15 8 87 9 2 4 67 66 5 5 9 +2591: 5 690 243 658 7 7 981 +11335843: 56 1 4 950 9 6 939 7 43 +339499: 484 89 7 76 1 +50590800: 32 9 28 697 582 1 8 2 9 +88685710: 6 795 677 5 5 2 24 9 10 +1332298351: 331 409 8 5 402 +5216655510: 9 2 9 842 1 93 1 59 946 +1659853: 6 952 8 34 9 288 36 73 +221772: 2 63 4 152 775 +40561164: 3 3 5 7 3 4 8 526 1 14 2 9 +23568090: 373 5 4 5 26 97 +13154240: 221 1 16 488 44 74 +101844827954: 193 811 69 8 943 +569733777404: 6 56 603 19 55 148 1 2 +5009649: 55 7 277 941 6 +6099: 659 9 7 7 539 41 794 +4940100131: 3 18 8 2 7 900 134 +281957: 48 1 89 6 11 5 +732142: 639 36 541 8 888 2 +2602791942: 9 7 1 48 111 64 449 2 4 +56636717: 566 123 199 45 17 +970: 1 870 5 7 87 1 +141997: 33 43 23 5 70 +14103: 4 3 42 9 3 495 +1380: 631 90 388 178 88 5 +121458: 5 42 4 27 3 704 6 2 5 1 +10563: 12 88 3 +12076236: 6 71 2 23 87 3 9 +407866358: 71 55 55 57 4 6 +220520: 1 575 17 4 74 5 +1136: 4 50 76 8 4 +47253: 5 39 45 61 5 63 4 +217056: 1 407 19 4 7 +221152: 59 9 4 87 5 2 +13785920220: 4 30 4 6 2 498 4 8 6 890 +131874141126: 4 5 8 4 471 664 9 6 689 +111356265089: 344 83 888 2 4 39 955 +50790109540: 9 8 7 1 6 7 7 6 63 8 905 +1832220: 28 39 4 1 6 1 9 90 3 78 +12894792: 1 739 15 40 2 646 29 +1953231: 496 6 8 82 975 +27468: 14 6 405 29 1 136 42 +207510: 190 2 543 746 424 +7901694458: 7 90 1 694 458 +14462447489: 469 9 81 47 39 9 55 34 +466: 4 453 9 +15487: 52 1 105 98 3 +4016027: 5 93 959 290 9 1 1 +575580136: 431 65 763 1 20 1 36 +62390487: 6 1 37 8 35 4 4 4 4 5 8 9 +5931562406: 8 1 443 8 3 548 8 2 40 7 +2919799584: 327 412 904 39 552 +10347578: 470 32 220 520 15 +111597016: 6 2 34 757 5 9 78 2 69 5 +55745928000: 36 490 46 100 687 +35306328: 52 82 92 15 24 44 6 +927133957: 95 9 7 66 966 +1407447: 4 81 10 72 164 +54563: 2 770 7 1 70 33 +25347499: 633 63 5 4 2 93 3 +15336: 8 6 41 160 5 8 955 2 9 +1494711: 9 68 9 1 7 707 678 29 5 +83914: 2 68 5 5 3 4 12 5 97 +15581702: 357 219 1 1 3 2 5 6 15 4 +691560: 549 2 32 1 612 +5331867755: 532 2 9 86 7 752 +87270: 83 4 1 91 79 +4113216: 662 2 8 4 772 +92741696518: 6 2 888 23 62 8 473 12 +1052128262: 1 583 5 15 3 3 7 3 18 +1353338: 33 22 7 400 4 431 +415568: 66 320 5 804 152 +6675614238: 11 12 60 23 6 8 35 4 3 +9944957: 496 63 55 2 8 70 57 5 +41348626: 6 2 17 87 1 4 2 87 6 1 94 +893: 6 42 25 3 308 299 6 +87913: 61 45 16 2 70 3 +7169002681: 9 112 889 885 4 +9502: 6 931 8 6 46 +1343859: 2 669 5 774 85 +90053666: 4 2 4 5 72 3 7 3 163 6 5 +177: 1 54 41 8 74 +169136269008: 7 302 20 1 2 2 3 4 25 4 8 +32232: 3 2 22 8 6 +14682648: 2 7 821 3 296 4 3 4 13 6 +1048320: 287 25 8 35 6 2 +218531768000: 518 5 65 20 854 76 +27683792: 3 87 15 837 92 +58809392926: 6 4 5 4 2 99 376 99 2 9 +211079278502: 69 8 7 54 5 5 4 9 8 5 4 5 +13246598290: 8 6 2 3 9 4 7 591 7 9 5 74 +9376859: 9 376 831 1 27 +2082539: 243 5 7 5 3 8 7 5 8 1 7 5 +1400284: 89 5 7 2 4 40 9 927 2 7 4 +226424872: 7 6 9 26 6 85 6 2 6 4 875 +339176384270: 2 9 8 43 13 9 1 3 9 2 272 +6516205: 3 8 21 6 57 5 7 8 43 7 3 7 +6108: 9 9 1 9 5 5 50 94 1 964 +153260: 2 8 3 5 343 8 7 8 2 27 4 2 +12245: 61 66 95 56 124 +42014: 670 5 297 6 2 +816034509: 815 937 9 966 11 +86314032: 40 62 322 73 36 +34387080: 928 6 6 3 26 469 +817: 8 34 9 3 580 90 93 +168614531: 2 58 51 5 35 3 5 83 31 +27105507: 7 360 9 7 6 779 4 83 24 +8887196118: 759 510 5 9 94 4 1 4 1 7 +1702560: 39 6 92 366 5 1 87 5 96 +15546951434: 7 44 8 8 79 59 5 1 7 8 8 1 +10416: 2 8 31 233 9 6 14 +10610: 37 16 3 6 340 5 +159259407: 1 284 41 558 3 626 +1619: 30 9 4 357 989 +4113024: 6 62 60 99 76 6 4 4 +15861472119: 99 4 5 327 41 4 11 9 +21606958622: 3 8 7 9 6 3 7 9 17 6 9 780 +13349890272: 824 9 6 1 3 6 5 5 27 2 +385229634: 3 70 68 69 4 77 33 514 +99572054: 7 6 7 682 73 5 4 +4296541: 41 40 717 5 607 5 5 41 +83272: 4 297 290 9 56 +139128713: 9 737 1 49 428 465 +4028: 85 84 192 9 779 +35364005: 5 8 37 4 1 810 7 9 7 1 9 +4871348: 9 1 291 31 60 8 +6377210: 899 22 8 5 6 1 7 322 +920026739491: 888 7 505 3 5 7 1 41 80 +664692813: 2 3 36 9 8 78 9 820 657 +448787603: 448 787 603 +2948: 862 74 3 51 89 +2861689: 51 63 6 919 7 47 +55: 33 3 3 9 7 +277382607: 5 658 42 843 201 +857871047: 324 445 850 7 47 +66: 8 3 42 +8829610: 6 9 317 3 1 3 543 96 10 +98780: 89 4 92 85 95 +1218692839027: 25 336 64 946 481 1 +333850065: 370 9 1 4 4 28 9 6 9 6 73 +5913270274: 49 674 7 9 2 95 5 3 9 1 4 +1058945504413: 3 961 599 28 219 11 +70200: 5 195 351 +6313662: 4 23 8 66 4 956 71 164 +7737319: 773 7 20 6 9 50 +8890876: 7 7 5 6 42 9 2 16 23 8 4 +702376: 557 17 128 375 1 +1703: 727 17 1 8 948 +764: 55 8 8 5 4 +12838: 76 9 654 3 9 4 +3017019296: 18 74 32 4 817 7 9 6 3 6 +315: 10 59 246 +7426403: 167 549 9 9 41 39 +65918934: 9 965 15 441 456 +1989: 3 6 20 917 884 9 +14938235506: 2 67 9 4 3 7 6 5 3 9 8 503 +2401280: 32 5 7 536 4 +25048829: 5 5 48 7 2 1 5 59 81 7 2 9 +34211: 965 39 2 34 5 +2097: 71 63 3 12 960 43 680 +4738230: 4 615 3 54 8 19 7 +505325940064: 109 6 62 926 86 824 +397: 320 1 77 +24283672: 6 508 738 98 64 91 61 +201570694: 411 3 5 7 7 2 7 7 901 2 7 +262710: 6 8 59 5 599 9 565 5 6 7 +234532856: 781 5 2 759 7 3 6 1 56 2 +12183: 8 2 61 60 93 +2853090: 6 2 4 51 9 31 9 7 305 2 9 +12424702: 303 5 94 9 858 4 +72081: 40 66 85 8 +797: 368 421 8 +7150889088: 247 718 3 3 4 64 9 56 8 +408693600: 1 41 8 850 159 9 +631864944414: 1 990 7 63 2 72 417 +3156310699: 5 7 7 370 7 7 9 3 25 4 29 +51034085: 30 1 17 340 85 +180215165: 6 462 541 65 +13656279: 13 650 6 56 79 +76570107: 924 88 933 931 12 +5959123: 5 68 5 1 175 198 +172434636: 492 5 59 7 22 6 3 3 +8263207829: 32 9 3 153 63 676 2 29 +563920: 2 56 6 317 848 +217867162: 2 1 893 8 8 5 1 8 5 759 7 +88528440000: 874 1 52 67 60 484 +276057738675: 4 7 4 3 9 2 698 7 4 75 9 +455996860826: 8 288 39 246 2 55 826 +97900: 90 871 7 84 4 821 56 8 +1170566: 9 8 7 1 2 7 4 49 2 61 7 +1071: 9 5 813 157 6 +1295167590: 998 312 112 77 9 6 9 +9425843: 533 8 131 133 +948765321: 8 16 4 5 9 59 6 96 2 1 3 3 +3773573: 94 33 4 3 71 +7654134670: 838 9 5 6 467 10 217 +3413788616: 254 336 4 2 8 616 +2560: 54 37 470 58 34 +16702: 6 3 1 9 8 7 2 8 3 7 4 +310762155391: 5 639 7 1 8 334 9 6 52 +3850516: 916 20 7 413 6 +6686193958: 5 8 8 861 93 958 +10864165: 1 782 365 3 2 99 38 3 +796: 6 25 7 2 5 4 +502: 44 3 9 8 54 +4282: 8 478 442 9 7 +14487: 5 4 61 228 5 +1296: 3 56 747 338 43 +5715: 83 1 6 35 9 786 1 6 70 1 +14601634503: 5 9 6 3 40 43 8 6 3 7 2 5 +17286066: 87 74 895 3 1 35 +1055582489: 561 48 188 79 8 +95925385: 958 6 4 60 970 416 +22657320: 8 66 420 9 81 +1039197692: 924 89 26 197 692 +217322: 97 96 8 614 15 23 +872: 1 7 72 +66985792: 8 1 3 70 9 79 7 4 8 597 3 +17112383: 8 9 83 3 3 7 5 81 5 8 1 64 +6521202: 4 666 9 2 8 959 +1835974: 25 41 6 6 683 96 6 +35551710: 5 136 7 6 4 95 3 5 6 9 27 +877173: 4 4 91 268 1 48 33 +838145: 9 57 5 4 837 6 3 6 7 7 5 +18944778: 17 621 7 707 6 6 +44370833: 80 7 510 83 4 +162305004887: 316 1 9 8 157 4 16 84 6 +2136101: 76 7 3 304 820 +1142: 6 18 85 34 8 +10018: 909 84 3 86 +4653: 6 31 680 275 4 89 +9748: 7 5 72 98 88 4 85 3 6 9 1 +950: 97 61 6 2 +398525404786: 48 6 82 54 10 47 82 2 +1379238: 597 330 9 15 7 +1230344192: 5 7 2 567 8 64 517 8 +13787542708: 492 4 12 2 7 51 4 906 +152385944894: 28 68 8 2 405 26 8 8 9 2 +15707756: 3 826 42 8 4 779 +7753272691: 7 85 596 7 28 7 9 8 691 +1708: 7 2 94 6 15 +11845226295: 79 8 601 5 33 3 7 945 +735050: 57 7 4 61 7 4 9 83 1 3 1 2 +611123: 216 9 93 3 2 3 +5615753409: 7 1 983 5 5 8 8 4 7 6 102 +47952: 1 98 493 9 9 +37889940: 300 7 85 22 66 +21892494438: 84 2 26 494 43 5 2 +580189817: 6 74 9 7 7 12 26 70 1 +92459016045: 3 513 3 90 988 18 46 +915252: 911 98 318 7 85 +305766: 6 9 7 5 231 63 7 12 1 5 +6449440: 3 61 4 94 40 +92466197: 9 1 1 9 2 2 6 5 9 44 2 251 +186845731583: 3 8 7 67 1 45 724 7 5 80 +183702: 5 113 65 5 2 75 +36586232: 45 4 238 854 872 +1788196: 16 83 8 15 5 168 4 +15343352929477: 2 2 9 98 3 5 9 623 8 7 71 +487792: 13 574 7 83 1 +142: 22 6 8 +2779: 2 9 36 88 80 6 9 737 53 +123532: 9 857 7 16 12 +239544489: 177 9 4 8 22 683 +542: 76 1 7 3 +21242616: 3 42 444 7 1 6 4 +1597155: 745 351 7 5 996 8 7 35 +49642: 7 24 18 1 5 3 7 5 2 +14083: 32 44 3 +589683738732: 9 84 78 3 738 726 3 +172847253: 122 463 510 5 2 5 3 1 6 +23852288: 4 8 8 3 7 31 6 9 725 46 4 +1648035: 4 8 33 5 312 172 503 +170073923: 589 5 5 275 6 17 7 7 5 +2685497: 597 87 4 109 83 81 9 5 +5720: 94 85 85 45 17 467 +15812857139: 479 8 379 6 71 27 257 +18980157: 9 64 4 246 3 55 6 8 253 +1209222918: 8 3 6 555 98 6 9 81 29 +7496879: 7 930 8 816 63 +1141455: 6 6 5 4 3 5 81 282 67 2 7 +5529605684: 960 64 9 56 84 +244490715: 3 540 1 7 7 77 4 8 31 5 +6361489: 7 951 8 6 90 +4548: 1 5 3 9 5 7 9 7 1 27 4 239 +2270948411828: 6 60 375 93 63 11 83 1 +5464: 4 19 45 615 8 +635976: 3 15 320 177 799 +937659501: 6 689 3 21 1 2 36 3 3 9 3 +4879488: 7 602 8 6 59 7 813 81 +11658: 2 5 6 5 31 8 +36245304: 327 46 659 97 3 378 +6424420: 803 8 420 +268121844: 4 965 494 91 12 560 4 +3878245224: 492 4 46 6 51 84 +152195: 52 3 291 +1590661: 9 5 4 4 1 5 85 9 77 226 +9304680: 1 44 56 5 53 418 4 +367057751: 2 2 3 23 4 1 3 73 2 43 1 +91504: 90 419 1 533 551 +75672050: 41 93 4 325 61 19 6 +509068: 27 6 394 7 6 807 6 5 3 +42718: 35 7 61 207 1 5 27 733 +1866207563: 9 331 4 5 682 74 3 +212: 18 108 12 2 72 +1179911124230: 6 6 2 9 17 86 567 1 15 2 +1313469: 35 8 33 5 384 9 189 +16267691664897: 4 3 42 3 52 162 72 89 7 +1245: 55 26 8 514 642 +851925: 307 925 3 +8002379: 6 4 7 3 589 38 1 +21323704: 5 3 3 1 39 7 540 9 776 +4788932613951: 1 2 57 7 9 3 26 1 3 95 1 1 +8025705: 4 4 983 4 889 9 97 605 +406158: 6 52 70 98 61 +89397997394: 3 96 903 997 3 94 +540540528: 1 3 7 3 66 30 3 6 1 15 5 +641004065: 29 27 5 3 6 364 65 +2464018: 246 3 7 11 309 +32540: 28 1 76 46 3 2 2 717 5 4 +1082: 66 2 955 6 53 +1648383: 7 1 5 879 25 30 223 3 +553368: 52 6 18 53 4 8 +610848: 404 24 9 7 +71050391: 38 3 7 319 89 +2897: 1 27 833 88 3 53 +1584602857: 90 584 80 62 4 474 +9811393: 3 9 4 7 94 1 8 7 81 7 9 9 +31048: 15 1 8 578 42 5 92 +274: 1 81 3 26 2 +105395693928: 3 67 61 6 791 4 906 +234903318531: 10 8 980 9 9 2 2 3 1 411 +6046371: 60 45 3 4 12 20 639 +15554843649: 1 5 5 1 544 4 436 49 +2593285843: 14 63 6 36 6 49 4 3 +946737179: 946 72 93 78 61 20 +1182: 991 5 62 8 9 6 1 1 9 86 6 +496: 40 3 45 3 1 1 +550094: 55 90 2 2 99 55 39 +67808: 42 3 7 62 5 21 +48854668: 7 2 7 2 75 6 426 1 5 6 70 +299551812: 1 906 6 656 77 84 +13631739: 878 2 671 187 47 +1320498929: 8 7 9 53 7 4 41 8 89 2 2 8 +34868: 54 5 652 48 741 +5856382: 1 5 631 3 18 449 8 49 +10440233176590: 42 242 669 511 5 402 +1964450: 44 63 920 7 4 76 +88562: 65 9 6 2 548 881 2 +885545005: 877 8 2 4 768 4 9 8 2 5 +443749232810: 6 288 9 63 14 7 9 56 +436160700: 354 594 8 1 8 810 6 93 +57136780: 1 9 97 909 8 18 1 4 95 3 +174254199: 829 720 61 9 21 +42509179: 9 2 461 5 1 92 177 2 +4793: 8 230 1 940 8 4 8 3 32 6 +3375756000: 723 5 4 4 1 9 2 25 46 9 5 +59196060: 7 372 3 6 28 3 9 3 4 315 +391933728: 54 26 753 9 47 57 6 9 9 +2651184: 40 7 4 342 76 2 +108123916802: 2 2 524 9 4 5 16 8 60 1 +31188: 3 552 2 8 4 7 59 9 2 676 +1023: 95 8 82 96 85 +79008445: 2 16 8 85 457 +396204: 71 77 327 7 822 +1183000: 8 91 65 25 1 +170526: 2 9 2 546 293 +16687881: 556 3 72 97 49 531 4 +290313: 51 8 1 6 982 6 4 8 1 6 6 9 +22722971148979: 35 6 397 881 54 2 9 79 +13723: 146 1 91 294 52 +214646851: 21 46 464 4 51 +578228: 57 789 33 1 7 diff --git a/lib/spice.zig b/lib/spice.zig new file mode 100644 index 0000000..0b5a3e8 --- /dev/null +++ b/lib/spice.zig @@ -0,0 +1,545 @@ +const std = @import("std"); + +// The overall design of Spice is as follows: +// - ThreadPool spawns threads which acts as background workers. +// - A Worker, while executing, will share one piece of work (`shared_job`). +// - A Worker, while waiting, will look for shared jobs by other workers. + +pub const ThreadPoolConfig = struct { + /// The number of background workers. If `null` this chooses a sensible + /// default based on your system (i.e. number of cores). + background_worker_count: ?usize = null, + + /// How often a background thread is interrupted to find more work. + heartbeat_interval: usize = 100 * std.time.ns_per_us, +}; + +pub const ThreadPool = struct { + allocator: std.mem.Allocator, + mutex: std.Thread.Mutex = .{}, + /// List of all workers. + workers: std.ArrayListUnmanaged(*Worker) = .{}, + /// List of all background workers. + background_threads: std.ArrayListUnmanaged(std.Thread) = .{}, + /// The background thread which beats. + heartbeat_thread: ?std.Thread = null, + /// A pool for the JobExecuteState, to minimize allocations. + execute_state_pool: std.heap.MemoryPool(JobExecuteState), + /// This is used to signal that more jobs are now ready. + job_ready: std.Thread.Condition = .{}, + /// This is used to wait for the background workers to be available initially. + workers_ready: std.Thread.Semaphore = .{}, + /// This is set to true once we're trying to stop. + is_stopping: bool = false, + + /// A timer which we increment whenever we share a job. + /// This is used to prioritize always picking the oldest job. + time: usize = 0, + + heartbeat_interval: usize, + + pub fn init(allocator: std.mem.Allocator) ThreadPool { + return ThreadPool{ + .allocator = allocator, + .execute_state_pool = std.heap.MemoryPool(JobExecuteState).init(allocator), + .heartbeat_interval = undefined, + }; + } + + /// Starts the thread pool. This should only be invoked once. + pub fn start(self: *ThreadPool, config: ThreadPoolConfig) void { + const actual_count = config.background_worker_count orelse (std.Thread.getCpuCount() catch @panic("getCpuCount error")) - 1; + + self.heartbeat_interval = config.heartbeat_interval; + self.background_threads.ensureUnusedCapacity(self.allocator, actual_count) catch @panic("OOM"); + self.workers.ensureUnusedCapacity(self.allocator, actual_count) catch @panic("OOM"); + + for (0..actual_count) |_| { + const thread = std.Thread.spawn(.{}, backgroundWorker, .{self}) catch @panic("spawn error"); + self.background_threads.append(self.allocator, thread) catch @panic("OOM"); + } + + self.heartbeat_thread = std.Thread.spawn(.{}, heartbeatWorker, .{self}) catch @panic("spawn error"); + + // Wait for all of them to be ready: + for (0..actual_count) |_| { + self.workers_ready.wait(); + } + } + + pub fn deinit(self: *ThreadPool) void { + // Tell all background workers to stop: + { + self.mutex.lock(); + defer self.mutex.unlock(); + + self.is_stopping = true; + self.job_ready.broadcast(); + } + + // Wait for background workers to stop: + for (self.background_threads.items) |thread| { + thread.join(); + } + + if (self.heartbeat_thread) |thread| { + thread.join(); + } + + // Free up memory: + self.background_threads.deinit(self.allocator); + self.workers.deinit(self.allocator); + self.execute_state_pool.deinit(); + self.* = undefined; + } + + fn backgroundWorker(self: *ThreadPool) void { + var w = Worker{ .pool = self }; + var first = true; + + self.mutex.lock(); + defer self.mutex.unlock(); + + self.workers.append(self.allocator, &w) catch @panic("OOM"); + + // We don't bother removing ourselves from the workers list of exit since + // this only happens when the whole thread pool is destroyed anyway. + + while (true) { + if (self.is_stopping) break; + + if (self._popReadyJob()) |job| { + // Release the lock while executing the job. + self.mutex.unlock(); + defer self.mutex.lock(); + + w.executeJob(job); + + continue; // Go straight to another attempt of finding more work. + } + + if (first) { + // Register that we are ready. + self.workers_ready.post(); + first = false; + } + + self.job_ready.wait(&self.mutex); + } + } + + fn heartbeatWorker(self: *ThreadPool) void { + // We try to make sure that each worker is being heartbeat at the + // fixed interval by going through the workers-list one by one. + var i: usize = 0; + + while (true) { + var to_sleep: u64 = self.heartbeat_interval; + + { + self.mutex.lock(); + defer self.mutex.unlock(); + + if (self.is_stopping) break; + + const workers = self.workers.items; + if (workers.len > 0) { + i %= workers.len; + workers[i].heartbeat.store(true, .monotonic); + i += 1; + to_sleep /= workers.len; + } + } + + std.time.sleep(to_sleep); + } + } + + pub fn call(self: *ThreadPool, comptime T: type, func: anytype, arg: anytype) T { + // Create an one-off worker: + + var worker = Worker{ .pool = self }; + { + self.mutex.lock(); + defer self.mutex.unlock(); + + self.workers.append(self.allocator, &worker) catch @panic("OOM"); + } + + defer { + self.mutex.lock(); + defer self.mutex.unlock(); + + for (self.workers.items, 0..) |worker_ptr, idx| { + if (worker_ptr == &worker) { + _ = self.workers.swapRemove(idx); + break; + } + } + } + + var t = worker.begin(); + return t.call(T, func, arg); + } + + /// The core logic of the heartbeat. Every executing worker invokes this periodically. + fn heartbeat(self: *ThreadPool, worker: *Worker) void { + @setCold(true); + + self.mutex.lock(); + defer self.mutex.unlock(); + + if (worker.shared_job == null) { + if (worker.job_head.shift()) |job| { + // Allocate an execute state for it: + const execute_state = self.execute_state_pool.create() catch @panic("OOM"); + execute_state.* = .{ + .result = undefined, + }; + job.setExecuteState(execute_state); + + worker.shared_job = job; + worker.job_time = self.time; + self.time += 1; + + self.job_ready.signal(); // wake up one thread + } + } + + worker.heartbeat.store(false, .monotonic); + } + + /// Waits for (a shared) job to be completed. + /// This returns `false` if it turns out the job was not actually started. + fn waitForJob(self: *ThreadPool, worker: *Worker, job: *Job) bool { + const exec_state = job.getExecuteState(); + + { + self.mutex.lock(); + defer self.mutex.unlock(); + + if (worker.shared_job == job) { + // This is the job we attempted to share with someone else, but before someone picked it up. + worker.shared_job = null; + self.execute_state_pool.destroy(exec_state); + return false; + } + + // Help out by picking up more work if it's available. + while (!exec_state.done.isSet()) { + if (self._popReadyJob()) |other_job| { + self.mutex.unlock(); + defer self.mutex.lock(); + + worker.executeJob(other_job); + } else { + break; + } + } + } + + exec_state.done.wait(); + return true; + } + + /// Finds a job that's ready to be executed. + fn _popReadyJob(self: *ThreadPool) ?*Job { + var best_worker: ?*Worker = null; + + for (self.workers.items) |other_worker| { + if (other_worker.shared_job) |_| { + if (best_worker) |best| { + if (other_worker.job_time < best.job_time) { + // Pick this one instead if it's older. + best_worker = other_worker; + } + } else { + best_worker = other_worker; + } + } + } + + if (best_worker) |worker| { + defer worker.shared_job = null; + return worker.shared_job; + } + + return null; + } + + fn destroyExecuteState(self: *ThreadPool, exec_state: *JobExecuteState) void { + self.mutex.lock(); + defer self.mutex.unlock(); + + self.execute_state_pool.destroy(exec_state); + } +}; + +pub const Worker = struct { + pool: *ThreadPool, + job_head: Job = Job.head(), + + /// A job (guaranteed to be in executing state) which other workers can pick up. + shared_job: ?*Job = null, + /// The time when the job was shared. Used for prioritizing which job to pick up. + job_time: usize = 0, + + /// The heartbeat value. This is set to `true` to signal we should do a heartbeat action. + heartbeat: std.atomic.Value(bool) = std.atomic.Value(bool).init(true), + + pub fn begin(self: *Worker) Task { + std.debug.assert(self.job_head.isTail()); + + return Task{ + .worker = self, + .job_tail = &self.job_head, + }; + } + + fn executeJob(self: *Worker, job: *Job) void { + var t = self.begin(); + job.handler.?(&t, job); + } +}; + +pub const Task = struct { + worker: *Worker, + job_tail: *Job, + + pub inline fn tick(self: *Task) void { + if (self.worker.heartbeat.load(.monotonic)) { + self.worker.pool.heartbeat(self.worker); + } + } + + pub inline fn call(self: *Task, comptime T: type, func: anytype, arg: anytype) T { + return callWithContext( + self.worker, + self.job_tail, + T, + func, + arg, + ); + } +}; + +// The following function's signature is actually extremely critical. We take in all of +// the task state (worker, last_heartbeat, job_tail) as parameters. The reason for this +// is that Zig/LLVM is really good at passing parameters in registers, but struggles to +// do the same for "fields in structs". In addition, we then return the changed value +// of last_heartbeat and job_tail. +fn callWithContext( + worker: *Worker, + job_tail: *Job, + comptime T: type, + func: anytype, + arg: anytype, +) T { + var t = Task{ + .worker = worker, + .job_tail = job_tail, + }; + t.tick(); + return @call(.always_inline, func, .{ + &t, + arg, + }); +} + +pub const JobState = enum { + pending, + queued, + executing, +}; + +// A job represents something which _potentially_ could be executed on a different thread. +// The jobs forms a doubly-linked list: You call `push` to append a job and `pop` to remove it. +const Job = struct { + handler: ?*const fn (t: *Task, job: *Job) void, + prev_or_null: ?*anyopaque, + next_or_state: ?*anyopaque, + + // This struct gets placed on the stack in _every_ frame so we're very cautious + // about the size of it. There's three possible states, but we don't use a union(enum) + // since this would actually increase the size. + // + // 1. pending: handler is null. a/b is undefined. + // 2. queued: handler is set. prev_or_null is `prev`, next_or_state is `next`. + // 3. executing: handler is set. prev_or_null is null, next_or_state is `*JobExecuteState`. + + /// Returns a new job which can be used for the head of a list. + fn head() Job { + return Job{ + .handler = undefined, + .prev_or_null = null, + .next_or_state = null, + }; + } + + pub fn pending() Job { + return Job{ + .handler = null, + .prev_or_null = undefined, + .next_or_state = undefined, + }; + } + + pub fn state(self: Job) JobState { + if (self.handler == null) return .pending; + if (self.prev_or_null != null) return .queued; + return .executing; + } + + pub fn isTail(self: Job) bool { + return self.next_or_state == null; + } + + fn getExecuteState(self: *Job) *JobExecuteState { + std.debug.assert(self.state() == .executing); + return @ptrCast(@alignCast(self.next_or_state)); + } + + pub fn setExecuteState(self: *Job, execute_state: *JobExecuteState) void { + std.debug.assert(self.state() == .executing); + self.next_or_state = execute_state; + } + + /// Pushes the job onto a stack. + fn push(self: *Job, tail: **Job, handler: *const fn (task: *Task, job: *Job) void) void { + std.debug.assert(self.state() == .pending); + defer std.debug.assert(self.state() == .queued); + + self.handler = handler; + tail.*.next_or_state = self; // tail.next = self + self.prev_or_null = tail.*; // self.prev = tail + self.next_or_state = null; // self.next = null + tail.* = self; // tail = self + } + + fn pop(self: *Job, tail: **Job) void { + std.debug.assert(self.state() == .queued); + std.debug.assert(tail.* == self); + const prev: *Job = @ptrCast(@alignCast(self.prev_or_null)); + prev.next_or_state = null; // prev.next = null + tail.* = @ptrCast(@alignCast(self.prev_or_null)); // tail = self.prev + self.* = undefined; + } + + fn shift(self: *Job) ?*Job { + const job = @as(?*Job, @ptrCast(@alignCast(self.next_or_state))) orelse return null; + + std.debug.assert(job.state() == .queued); + + const next: ?*Job = @ptrCast(@alignCast(job.next_or_state)); + // Now we have: self -> job -> next. + + // If there is no `next` then it means that `tail` actually points to `job`. + // In this case we can't remove `job` since we're not able to also update the tail. + if (next == null) return null; + + defer std.debug.assert(job.state() == .executing); + + next.?.prev_or_null = self; // next.prev = self + self.next_or_state = next; // self.next = next + + // Turn the job into "executing" state. + job.prev_or_null = null; + job.next_or_state = undefined; + return job; + } +}; + +const max_result_words = 4; + +const JobExecuteState = struct { + done: std.Thread.ResetEvent = .{}, + result: ResultType, + + const ResultType = [max_result_words]u64; + + fn resultPtr(self: *JobExecuteState, comptime T: type) *T { + if (@sizeOf(T) > @sizeOf(ResultType)) { + @compileError("value is too big to be returned by background thread"); + } + + const bytes = std.mem.sliceAsBytes(&self.result); + return std.mem.bytesAsValue(T, bytes); + } +}; + +pub fn Future(comptime Input: type, Output: type) type { + return struct { + const Self = @This(); + + job: Job, + input: Input, + + pub inline fn init() Self { + return Self{ .job = Job.pending(), .input = undefined }; + } + + /// Schedules a piece of work to be executed by another thread. + /// After this has been called you MUST call `join` or `tryJoin`. + pub inline fn fork( + self: *Self, + task: *Task, + comptime func: fn (task: *Task, input: Input) Output, + input: Input, + ) void { + const handler = struct { + fn handler(t: *Task, job: *Job) void { + const fut: *Self = @fieldParentPtr("job", job); + const exec_state = job.getExecuteState(); + const value = t.call(Output, func, fut.input); + exec_state.resultPtr(Output).* = value; + exec_state.done.set(); + } + }.handler; + self.input = input; + self.job.push(&task.job_tail, handler); + } + + /// Waits for the result of `fork`. + /// This is only safe to call if `fork` was _actually_ called. + /// Use `tryJoin` if you conditionally called it. + pub inline fn join( + self: *Self, + task: *Task, + ) ?Output { + std.debug.assert(self.job.state() != .pending); + return self.tryJoin(task); + } + + /// Waits for the result of `fork`. + /// This function is safe to call even if you didn't call `fork` at all. + pub inline fn tryJoin( + self: *Self, + task: *Task, + ) ?Output { + switch (self.job.state()) { + .pending => return null, + .queued => { + self.job.pop(&task.job_tail); + return null; + }, + .executing => return self.joinExecuting(task), + } + } + + fn joinExecuting(self: *Self, task: *Task) ?Output { + @setCold(true); + + const w = task.worker; + const pool = w.pool; + const exec_state = self.job.getExecuteState(); + + if (pool.waitForJob(w, &self.job)) { + const result = exec_state.resultPtr(Output).*; + pool.destroyExecuteState(exec_state); + return result; + } + + return null; + } + }; +} diff --git a/utils.zig b/lib/utils.zig similarity index 86% rename from utils.zig rename to lib/utils.zig index 8857085..b0c33ed 100644 --- a/utils.zig +++ b/lib/utils.zig @@ -1,55 +1,5 @@ const std = @import("std"); -pub fn rangeComptime(comptime n: usize) [n]usize { - var array: [n]usize = undefined; - - for (0.., &array) |i, *elem| { - elem.* = i; - } - - return array; -} - -pub fn range(alloc: std.mem.Allocator, n: usize) ![]usize { - var array = try alloc.alloc(usize, n); - - for (0..n) |i| { - array[i] = i; - } - - return array; -} - -pub fn printSlice(comptime T: type, slice: []const T) void { - if (slice.len == 0) { - std.debug.print("[ ]", .{}); - return; - } - - std.debug.print("[ ", .{}); - - for (slice[0 .. slice.len - 1]) |x| { - std.debug.print("{}, ", .{x}); - } - - std.debug.print("{} ]", .{slice[slice.len - 1]}); -} - -pub fn printlnSlice(comptime T: type, slice: []const T) void { - if (slice.len == 0) { - std.debug.print("[ ]\n", .{}); - return; - } - - std.debug.print("[ ", .{}); - - for (slice[0 .. slice.len - 1]) |x| { - std.debug.print("{}, ", .{x}); - } - - std.debug.print("{} ]\n", .{slice[slice.len - 1]}); -} - pub const FileReader = struct { const BufferedReader = std.io.BufferedReader(4096, std.fs.File.Reader); const Reader = std.io.Reader(*BufferedReader, std.fs.File.Reader.Error, BufferedReader.read); @@ -149,3 +99,81 @@ pub fn numberParser(comptime T: type, input: []const u8) NumberParser(T) { pub fn numberParserWithDelimiter(comptime T: type, input: []const u8, delimiter: u8) NumberParser(T) { return NumberParser(T){ .token_it = std.mem.tokenizeScalar(u8, input, delimiter) }; } + +pub fn rangeComptime(comptime n: usize) [n]usize { + var array: [n]usize = undefined; + + for (0.., &array) |i, *elem| { + elem.* = i; + } + + return array; +} + +pub fn range(alloc: std.mem.Allocator, n: usize) ![]usize { + var array = try alloc.alloc(usize, n); + + for (0..n) |i| { + array[i] = i; + } + + return array; +} + +pub fn printSlice(comptime T: type, slice: []const T) void { + if (slice.len == 0) { + std.debug.print("[ ]", .{}); + return; + } + + std.debug.print("[ ", .{}); + + for (slice[0 .. slice.len - 1]) |x| { + std.debug.print("{}, ", .{x}); + } + + std.debug.print("{} ]", .{slice[slice.len - 1]}); +} + +pub fn printlnSlice(comptime T: type, slice: []const T) void { + if (slice.len == 0) { + std.debug.print("[ ]\n", .{}); + return; + } + + std.debug.print("[ ", .{}); + + for (slice[0 .. slice.len - 1]) |x| { + std.debug.print("{}, ", .{x}); + } + + std.debug.print("{} ]\n", .{slice[slice.len - 1]}); +} + +pub fn num_digits(n: anytype) std.math.Log2Int(@TypeOf(n)) { + return std.math.log10_int(n) + 1; +} + +test "num_digits" { + // try std.fmt.allocPrint( + // std.testing.allocator, + // "Expected {} to have 4 digit, but got {} instead", + // .{ n, num_digits }, + // ); + + for (2..10) |n| { + try std.testing.expectEqual(num_digits(n), 1); + } + + for (10..100) |n| { + try std.testing.expectEqual(num_digits(n), 2); + } + + for (100..1_000) |n| { + try std.testing.expectEqual(num_digits(n), 3); + } + + for (1_000..10_000) |n| { + try std.testing.expectEqual(num_digits(n), 4); + } +}