Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2024/src/day22.zig
2024-12-25 01:06:01 -08:00

122 lines
3.3 KiB
Zig

const std = @import("std");
const aoc = @import("aoc.zig");
const Allocator = std.mem.Allocator;
const List = std.ArrayList;
const Map = std.AutoHashMap;
const Set = aoc.AutoHashSet;
const PriorityQueue = std.PriorityQueue;
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const text = try aoc.readAll(allocator, "input/day22.txt");
defer allocator.free(text);
var lines = std.mem.tokenizeScalar(u8, text, '\n');
var seeds = List(u64).init(allocator);
defer seeds.deinit();
while (lines.next()) |line| {
const seed = try std.fmt.parseInt(u64, line, 10);
try seeds.append(seed);
}
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
const stdout = bw.writer();
try stdout.print("{}\n{}\n", try solve(allocator, seeds.items));
try bw.flush();
}
fn solve(allocator: Allocator, seeds: []u64) !struct { u64, u64 } {
var totals = Map([4]i64, u64).init(allocator);
defer totals.deinit();
var sum: u64 = 0;
for (seeds) |seed| {
var profit = Map([4]i64, u64).init(allocator);
defer profit.deinit();
sum += try populate(seed, 2000, &profit);
var it = profit.iterator();
while (it.next()) |entry| {
const changes, const bananas = .{ entry.key_ptr.*, entry.value_ptr.* };
const total = try totals.getOrPutValue(changes, 0);
total.value_ptr.* += bananas;
}
}
var best: u64 = 0;
var it = totals.valueIterator();
while (it.next()) |p| {
best = @max(best, p.*);
}
return .{ sum, best };
}
fn populate(seed: u64, steps: usize, profit: *Map([4]i64, u64)) !u64 {
var n: u64 = seed;
var prices = [_]u64{0} ** 4;
// Pre-fill the first few updates, since those don't have enough changes yet.
for (0..3) |i| {
const price = n % 10;
prices[i] = price;
n = ((n * 64) ^ n) % 16777216;
n = ((n / 32) ^ n) % 16777216;
n = ((n * 2048) ^ n) % 16777216;
}
var changes = [_]i64{0} ** 4;
for (1..4) |i| {
const next: i64 = @intCast(prices[i]);
const prev: i64 = @intCast(prices[i - 1]);
changes[i] = next - prev;
}
// This is the first time we have a usable change sequence.
_ = try profit.getOrPutValue(changes, prices[3]);
for (3..steps) |_| {
n = ((n * 64) ^ n) % 16777216;
n = ((n / 32) ^ n) % 16777216;
n = ((n * 2048) ^ n) % 16777216;
const price = n % 10;
std.mem.copyForwards(u64, &prices, prices[1..]);
prices[3] = price;
std.mem.copyForwards(i64, &changes, changes[1..]);
{
const a: i64 = @intCast(prices[3]);
const b: i64 = @intCast(prices[2]);
changes[3] = a - b;
}
_ = try profit.getOrPutValue(changes, prices[3]);
}
// Everything but the update, because we *can* still sell here.
{
const price = n % 10;
std.mem.copyForwards(u64, &prices, prices[1..]);
prices[3] = price;
std.mem.copyForwards(i64, &changes, changes[1..]);
{
const a: i64 = @intCast(prices[3]);
const b: i64 = @intCast(prices[2]);
changes[3] = a - b;
}
_ = try profit.getOrPutValue(changes, prices[3]);
}
return n;
}