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); }