From 00358500500f7f870e52c91e65cf3263e642f160 Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Sun, 13 Dec 2020 20:57:27 -0500 Subject: [PATCH] Finish day 13 --- day13/build.zig | 27 +++ day13/input | 2 + day13/src/main.zig | 467 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 496 insertions(+) create mode 100644 day13/build.zig create mode 100644 day13/input create mode 100644 day13/src/main.zig diff --git a/day13/build.zig b/day13/build.zig new file mode 100644 index 0000000..eda46ed --- /dev/null +++ b/day13/build.zig @@ -0,0 +1,27 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard release options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("day13", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/day13/input b/day13/input new file mode 100644 index 0000000..73a2112 --- /dev/null +++ b/day13/input @@ -0,0 +1,2 @@ +1005595 +41,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,37,x,x,x,x,x,557,x,29,x,x,x,x,x,x,x,x,x,x,13,x,x,x,17,x,x,x,x,x,23,x,x,x,x,x,x,x,419,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,19 diff --git a/day13/src/main.zig b/day13/src/main.zig new file mode 100644 index 0000000..b1bdc49 --- /dev/null +++ b/day13/src/main.zig @@ -0,0 +1,467 @@ +const std = @import("std"); + +pub fn main() anyerror!void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + var gpa = &arena.allocator; + + var f = try std.fs.cwd().openFile("input", .{}); + var contents = try f.readToEndAlloc(gpa, std.math.maxInt(u32)); + defer gpa.free(contents); + + var buses = std.ArrayList(Bus).init(gpa); + defer buses.deinit(); + + var it = std.mem.tokenize(contents, "\n"); + var estimated_arrival_at_bus_station: u64 = 0; + if (it.next()) |l| { + estimated_arrival_at_bus_station = try std.fmt.parseUnsigned(u64, l, 10); + } + if (it.next()) |l| { + var bus_it = std.mem.tokenize(l, ","); + var offset: u64 = 0; + while (bus_it.next()) |b| : (offset += 1) { + if (b[0] == 'x') { + continue; + } + var a = std.fmt.parseUnsigned(u64, b, 10) catch continue; + var bus = Bus{.id = a, .offset = offset}; + std.log.debug("Bus: {}", .{bus}); + try buses.append(bus); + } + } + + var earliest_bus = find_earliest_bus_after(buses.items, estimated_arrival_at_bus_station); + var wait_time = earliest_bus.earliest_departure_after(estimated_arrival_at_bus_station) - estimated_arrival_at_bus_station; + std.log.info("Earliest bus '{}' will depart at {}, a wait of {}.", + .{earliest_bus.id, earliest_bus.earliest_departure_after(estimated_arrival_at_bus_station), wait_time}); + std.log.info("Part one: {}", .{earliest_bus.id * wait_time}); + + // Part 2 + // the challenge warns us it will "surely be" above 100000000000000 + // 18446744073709551615 <-- u64 max + //var est_start : u64 = buses.items[0].earliest_departure_after(estimated_arrival_at_bus_station); + //est_start = 100000733946084; + est_start = 101775718927832; + // From Est start 101775718927832 + // To 140354342739376 took 180.75m of CPU time, + // 598411311431841 <-- sol'n after using sieve + lcm + // It would have probably taken another (180.75)*3.26 = 9.82 hours to get there... + // lcm + sieve: ~ 0.003s, and starts working from 0. + var prize_start : u64 = 0; + var act = std.os.Sigaction { + .sigaction = handle_sigusr, + //.sigaction = std.os.linux.SIG_IGN, + .mask = std.os.empty_sigset, + .flags = 0, + }; + std.os.sigaction(10, &act, null); + // Brute force approach + // while (prize_start == 0) : (est_start += buses.items[0].id) { + // var matches = true; + // for (buses.items[1..]) |b, k| { + // if (@mod(est_start + b.offset, b.id) % b.id != 0) { + // matches = false; + // //std.log.debug("Bus {} failed iteration starting at {}", .{b.id, est_start}); + // break; + // } + // } + // if (matches) { + // prize_start = est_start; + // } + // } + prize_start = try find_bus_offset_departure(buses.items[0..], gpa); + std.log.info("Earliest depature match prize condition: {}", .{prize_start}); +} + +var t_last_sigusr : i128 = 0; +var est_start : u64 = 0; +var last_start_value : u64 = 0; +fn handle_sigusr(sig: i32, info: *std.os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) void { + var values_per_second_avg : f64 = 0.0; + if (last_start_value == 0) { + std.log.warn("Last {}", .{est_start}); + } + else { + var calcs_done : f64 = @intToFloat(f64, (est_start - last_start_value)/41); + values_per_second_avg = calcs_done + / @intToFloat(f64, std.time.nanoTimestamp() - t_last_sigusr); + std.log.warn("Last {}, {}/s avg - {} calcs done", + .{est_start, values_per_second_avg * std.time.ns_per_s, calcs_done}); + } + last_start_value = est_start; + t_last_sigusr = std.time.nanoTimestamp(); +} + +fn find_earliest_bus_after(buses: []Bus, time: u64) Bus { + var earliest_bus_index : u64 = 0; + var earliest_departure_time : u64 = std.math.maxInt(u64); + for (buses) |bus, k| { + var departs_at : u64 = Bus.earliest_departure(bus.id, time); + if (departs_at < earliest_departure_time) { + earliest_bus_index = k; + earliest_departure_time = departs_at; + } + } + return buses[earliest_bus_index]; +} + +const Bus = struct { + id: u64, + offset: u64 = 0, + + pub fn earliest_departure_after(self: *Bus, time: u64) u64 { + return Bus.earliest_departure(self.id, time); + } + + pub fn earliest_departure(id: u64, time: u64) u64 { + var d : u64 = ((time / id)+1) * id; + std.debug.assert(d >= time); + return d; + } +}; + + +fn find_bus_offset_departure(buses: []Bus, a: *std.mem.Allocator) !u64 { + var list_pos : usize = 0; + var t : u64 = 0; + var increment : u64 = buses[0].id; + var bus_ids = try a.alloc(u64, buses.len); + defer a.free(bus_ids); + for (buses) |b, k| { + bus_ids[k] = b.id; + } + while (list_pos < (buses.len-1)) : (t += increment) { + // Check if (t+buses[list_pos+1].offset)%buses[list_pos+1].id == 0 + if (@mod(t+buses[list_pos+1].offset, buses[list_pos+1].id) == 0) { + // Our bus arrives here, great. + increment = try lcm_by_factorization(std.testing.allocator, bus_ids[0..list_pos+2]); + std.log.debug("Bus {} (offset: {}) in pos {} at {}. New increment {}", + .{buses[list_pos+1].id, buses[list_pos+1].offset, list_pos+1, t, increment}); + list_pos += 1; + } + } + // If we left the loop, we incremented, so decrement + t -= increment; + return t; +} + +test "departure" { + var a : u64 = 939; + std.testing.expectEqual(Bus.earliest_departure(59, 939), 944); +} + +test "prize_start" { + // 7,13,x,x,59,x,31,19 + var buses = [_]Bus { + Bus{.id = 7}, + Bus{.id = 13, .offset = 1}, + Bus{.id = 59, .offset = 4}, + Bus{.id = 31, .offset = 6}, + Bus{.id = 19, .offset = 7}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 1068781); + + // No LCM version, because the example's prize_start if based on the + // arbitrary offset. +} + +test "prize_ex1" { + //17,x,13,19 + var buses = [_]Bus { + Bus{.id = 17}, + Bus{.id = 13, .offset = 2}, + Bus{.id = 19, .offset = 3}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 3417); + // LCM version + prize_start = try find_bus_offset_departure(buses[0..], std.testing.allocator); + std.testing.expectEqual(prize_start, 3417); +} + +test "prize_ex2" { + // 67,7,59,61 + var buses = [_]Bus { + Bus{.id = 67}, + Bus{.id = 7, .offset = 1}, + Bus{.id = 59, .offset = 2}, + Bus{.id = 61, .offset = 3}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 754018); + + // LCM version + prize_start = try find_bus_offset_departure(buses[0..], std.testing.allocator); + std.testing.expectEqual(prize_start, 754018); +} + +test "prize_ex3" { + // 67,x,7,59,61 + var buses = [_]Bus { + Bus{.id = 67}, + Bus{.id = 7, .offset = 2}, + Bus{.id = 59, .offset = 3}, + Bus{.id = 61, .offset = 4}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 779210); + // LCM version + prize_start = try find_bus_offset_departure(buses[0..], std.testing.allocator); + std.testing.expectEqual(prize_start, 779210); +} + +test "prize_ex4" { + // 67,7,x, 59,61 + var buses = [_]Bus { + Bus{.id = 67}, + Bus{.id = 7, .offset = 1}, + Bus{.id = 59, .offset = 3}, + Bus{.id = 61, .offset = 4}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 1261476); + // LCM version + prize_start = try find_bus_offset_departure(buses[0..], std.testing.allocator); + std.testing.expectEqual(prize_start, 1261476); +} + +test "prize_ex5" { + // 1789,37,47,1889 + var buses = [_]Bus { + Bus{.id = 1789}, + Bus{.id = 37, .offset = 1}, + Bus{.id = 47, .offset = 2}, + Bus{.id = 1889, .offset = 3}, + }; + est_start = 0; + var prize_start : u64 = 0; + while (prize_start == 0) : (est_start += buses[0].id) { + var matches = true; + for (buses[1..]) |b, k| { + if ((est_start + b.offset) % b.id != 0) { + matches = false; + break; + } + } + if (matches) { + prize_start = est_start; + } + } + std.log.warn("Earliest depature match prize condition: {}", .{prize_start}); + std.testing.expectEqual(prize_start, 1202161486); + // LCM version + prize_start = try find_bus_offset_departure(buses[0..], std.testing.allocator); + std.testing.expectEqual(prize_start, 1202161486); +} + +fn lcm_by_factorization(allocator: *std.mem.Allocator, values: []u64) !u64 { + var highest_powers = std.hash_map.AutoHashMap(u64, u64).init(allocator); + defer highest_powers.deinit(); + + // For each item in the set, get the prime factorization of the item's value + for (values) |v| { + var list = try prime_factors(v, allocator); + defer list.deinit(); + for (list.items) |factor| { + std.log.warn("{}^{} is a prime factor of {}", .{factor.base, factor.power, v}); + if (highest_powers.get(factor.base)) |other_power| { + if (factor.power > other_power) { + highest_powers.putAssumeCapacity(factor.base, factor.power); + } + } + else { + try highest_powers.put(factor.base, factor.power); + } + } + } + + var lcm : u64 = 1; + var it = highest_powers.iterator(); + while (it.next()) |entry| { + std.log.warn("{}^{} is part of the LCM", .{entry.key, entry.value}); + lcm *= std.math.pow(u64, entry.key, entry.value); + } + + return lcm; +} + +const Factor = struct { + base: u64, + power: u64 = 1, +}; + +fn prime_factors(value: u64, allocator: *std.mem.Allocator) !std.ArrayList(Factor) { + var factors = std.ArrayList(Factor).init(allocator); + // Using the "test and fail method" + var current : u64 = value; + var divisor : u64 = 2; + if (is_prime(value)) { + try factors.append(Factor{.base = value}); + return factors; + } + while(current != 1) { + //std.log.warn("{} rem {} = {}", .{current, divisor, @rem(current, divisor)}); + if (@rem(current, divisor) == 0) { + current = current / divisor; + // Add divisor to the list of factors if it's not there, otherwise + // increment the power + var index: ?usize = null; + for (factors.items) |v, k| { + if (v.base == divisor) { + index = k; + break; + } + } + if (index) |i| { + factors.items[i].power += 1; + } + else { + try factors.append(Factor{.base = divisor}); + } + } + else { + // Out of this factor, increment the divisor until the next prime number + // divisor is reached. + divisor += 1; + while(!is_prime(divisor)) : (divisor+=1) {} + } + } + + return factors; +} + +fn is_prime(value: u64) bool { + if (value == 1) { + return false; + } + var i: u64 = 2; + while (i < (value-1)) : (i += 1) { + if (@rem(value, i) == 0) { + return false; + } + } + return true; +} + +test "is_prime_49" { + std.testing.expect(is_prime(49) == false); +} + +test "is_prime_1009" { + std.testing.expect(is_prime(1009)); +} + +test "prime_factors_100" { + var a = std.testing.allocator; + var factors = try prime_factors(100, a); + defer factors.deinit(); + // for (factors.items) |v| { + // std.log.warn("{}^{} ", .{v.base, v.power}); + // } + std.testing.expectEqual(factors.items.len, 2); + std.testing.expectEqual(factors.items[0].base, 2); + std.testing.expectEqual(factors.items[0].power, 2); + std.testing.expectEqual(factors.items[1].base, 5); + std.testing.expectEqual(factors.items[0].power, 2); +} + +test "prime_factors_1009" { + var a = std.testing.allocator; + var factors = try prime_factors(1009, a); + defer factors.deinit(); + // for (factors.items) |v| { + // std.log.warn("{}^{} ", .{v.base, v.power}); + // } + std.testing.expectEqual(factors.items.len, 1); + std.testing.expectEqual(factors.items[0].base, 1009); + std.testing.expectEqual(factors.items[0].power, 1); +} + +// This is a pretty long test... +test "prime_factors_1289732" { + var a = std.testing.allocator; + var factors = try prime_factors(128973, a); + defer factors.deinit(); + // for (factors.items) |v| { + // std.log.warn("{}^{} ", .{v.base, v.power}); + // } + std.testing.expectEqual(factors.items.len, 3); + std.testing.expectEqual(factors.items[0].base, 3); + std.testing.expectEqual(factors.items[0].power, 1); + std.testing.expectEqual(factors.items[1].base, 13); + std.testing.expectEqual(factors.items[1].power, 1); + std.testing.expectEqual(factors.items[2].base, 3307); + std.testing.expectEqual(factors.items[2].power, 1); +}