Finish day 17

This commit is contained in:
Kienan Stewart 2020-12-17 03:09:43 -05:00
parent dcc4dfce6a
commit b28fd56a8a
3 changed files with 668 additions and 0 deletions

27
day17/build.zig Normal file
View File

@ -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("day17", "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);
}

8
day17/input Normal file
View File

@ -0,0 +1,8 @@
####...#
......#.
#..#.##.
.#...#.#
..###.#.
##.###..
.#...###
.##....#

633
day17/src/main.zig Normal file
View File

@ -0,0 +1,633 @@
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(u64));
defer gpa.free(contents);
var sim = try Simulation.init(gpa);
defer sim.deinit();
var sim4d = try Simulation4D.init(gpa);
defer sim4d.deinit();
var it = std.mem.tokenize(contents, "\n");
var y : i64 = 0;
while (it.next()) |line| {
for (line) |c, k| {
var state : CubeState = .inactive;
if (c == '#') {
state = .active;
}
try sim.add_cube([_]i64 {@intCast(i64, k), y, 0}, state);
try sim4d.add_cube([_]i64 {@intCast(i64, k), y, 0, 0}, state);
}
y += 1;
}
// Run 6 rounds
try sim.do_round(); // 1
try sim.do_round(); // 2
try sim.do_round(); // 3
try sim.do_round(); // 4
try sim.do_round(); // 5
try sim.do_round(); // 6
std.log.info("After 6 rounds, there are {} active cubes",
.{sim.count_active_cubes()});
// Part 2
try sim4d.do_round(); // 1
try sim4d.do_round(); // 2
try sim4d.do_round(); // 3
try sim4d.do_round(); // 4
try sim4d.do_round(); // 5
try sim4d.do_round(); // 6
std.log.info("After 6 rounds, there are {} active cubes",
.{sim4d.count_active_cubes()});
}
const CubeState = enum {
inactive,
active
};
const Cube = struct {
pos: [3]i64,
state: CubeState = .inactive,
next_state: ?CubeState = null,
neighbours: u64 = 0,
pub fn get_neighbour_positions(self: *Cube) [26][3]i64 {
return [_][3]i64 {
// z-1
[_]i64 {self.pos[0], self.pos[1], self.pos[2]-1},
[_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]-1},
[_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]-1},
[_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]-1},
[_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]-1},
[_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]-1},
[_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]-1},
[_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]-1},
[_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]-1},
// z+0
[_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]},
[_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]},
[_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]},
[_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]},
[_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]},
[_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]},
[_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]},
[_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]},
// z+1
[_]i64 {self.pos[0], self.pos[1], self.pos[2]+1},
[_]i64 {self.pos[0], self.pos[1]+1, self.pos[2]+1},
[_]i64 {self.pos[0], self.pos[1]-1, self.pos[2]+1},
[_]i64 {self.pos[0]-1, self.pos[1], self.pos[2]+1},
[_]i64 {self.pos[0]-1, self.pos[1]+1, self.pos[2]+1},
[_]i64 {self.pos[0]-1, self.pos[1]-1, self.pos[2]+1},
[_]i64 {self.pos[0]+1, self.pos[1], self.pos[2]+1},
[_]i64 {self.pos[0]+1, self.pos[1]+1, self.pos[2]+1},
[_]i64 {self.pos[0]+1, self.pos[1]-1, self.pos[2]+1},
};
}
pub fn print_state(self: *Cube) !void {
var stdout = std.io.getStdOut().writer();
var c : u8 = '.';
if (self.state == .active) {
c = '#';
}
try stdout.print("({}x, {}y, {}z) {c}\n", .{self.pos[0], self.pos[1], self.pos[2], c});
}
};
const Simulation = struct {
map: std.hash_map.AutoHashMap([3]i64, Cube),
allocator: *std.mem.Allocator,
pub fn init(allocator: *std.mem.Allocator) !*Simulation {
var self = try allocator.create(Simulation);
errdefer allocator.destroy(self);
self.* = Simulation {
.map = std.hash_map.AutoHashMap([3]i64, Cube).init(allocator),
.allocator = allocator,
};
return self;
}
pub fn deinit(self: *Simulation) void {
self.map.deinit();
self.allocator.destroy(self);
}
pub fn add_cube(self: *Simulation, pos: [3]i64, state: CubeState) !void {
try self.map.put(pos, Cube {.pos = pos, .state = state});
}
pub fn print_state(self: *Simulation) !void {
// @TODO Organize the output somehow
var stdout = std.io.getStdOut().writer();
var z_min : i64 = std.math.maxInt(i64);
var z_max : i64 = std.math.minInt(i64);
var y_min : i64 = std.math.maxInt(i64);
var y_max : i64 = std.math.minInt(i64);
var x_min : i64 = std.math.maxInt(i64);
var x_max : i64 = std.math.minInt(i64);
var it = self.map.iterator();
while (it.next()) |entry| {
//try entry.value.print_state();
x_max = std.math.max(x_max, entry.value.pos[0]);
x_min = std.math.min(x_min, entry.value.pos[0]);
y_max = std.math.max(y_max, entry.value.pos[1]);
y_min = std.math.min(y_min, entry.value.pos[1]);
z_max = std.math.max(z_max, entry.value.pos[2]);
z_min = std.math.min(z_min, entry.value.pos[2]);
}
// For from lowest to highest then
var z_pos = z_min;
while (z_pos <= z_max) : (z_pos += 1) {
try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max});
var y_pos = y_min;
while (y_pos <= y_max) : (y_pos += 1) {
var x_pos = x_min;
while (x_pos <= x_max) : (x_pos += 1) {
//try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos});
var c : u8 = '.';
if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| {
if (cube.state == .active) {
c = '#';
}
}
try stdout.print("{c}", .{c});
}
_ = try stdout.write("\n");
}
_ = try stdout.write("\n");
}
_ = try stdout.write("\n");
}
pub fn print_state_neighbour_count (self: *Simulation) !void {
// @TODO Organize the output somehow
var stdout = std.io.getStdOut().writer();
var z_min : i64 = std.math.maxInt(i64);
var z_max : i64 = std.math.minInt(i64);
var y_min : i64 = std.math.maxInt(i64);
var y_max : i64 = std.math.minInt(i64);
var x_min : i64 = std.math.maxInt(i64);
var x_max : i64 = std.math.minInt(i64);
var it = self.map.iterator();
while (it.next()) |entry| {
//try entry.value.print_state();
x_max = std.math.max(x_max, entry.value.pos[0]);
x_min = std.math.min(x_min, entry.value.pos[0]);
y_max = std.math.max(y_max, entry.value.pos[1]);
y_min = std.math.min(y_min, entry.value.pos[1]);
z_max = std.math.max(z_max, entry.value.pos[2]);
z_min = std.math.min(z_min, entry.value.pos[2]);
}
// For from lowest to highest then
var z_pos = z_min;
while (z_pos <= z_max) : (z_pos += 1) {
try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max});
var y_pos = y_min;
while (y_pos <= y_max) : (y_pos += 1) {
var x_pos = x_min;
while (x_pos <= x_max) : (x_pos += 1) {
//try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos});
var c : u64 = 0;
if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| {
c = cube.neighbours;
}
try stdout.print("{:02}", .{c});
}
_ = try stdout.write("\n");
}
_ = try stdout.write("\n");
}
_ = try stdout.write("\n");
}
pub fn do_round(self: *Simulation) !void {
// For each cube, check all of it's neighbours
var it = self.map.iterator();
var neighbours_to_add = std.ArrayList([3]i64).init(self.allocator);
defer neighbours_to_add.deinit();
// Get the neighbours we need possibly create for this round
while (it.next()) |entry| {
var neighbours = entry.value.get_neighbour_positions();
var n_active : u64 = 0;
for (neighbours) |n_pos| {
if (self.map.get(n_pos)) |n| {
// Noop, we already have a cube
}
else {
// The neighbour doesn't yet exist, we should create it
// but we don't want to do that while iterating over
// the current entries.
// We also want to make sure that we're not adding a
// value that's already in the list to be added.
// For the moment, we'll check that when iterating
// over neighbours_to_add.
try neighbours_to_add.append(n_pos);
}
}
}
// Add any new neighbours
for (neighbours_to_add.items) |new_pos| {
// Double-check we don't already have this neighbour
if (self.map.get(new_pos)) |v| {
continue;
}
else {
try self.map.putNoClobber(new_pos, Cube {.pos = new_pos, .state = .inactive});
}
}
self.calculate_neighbour_states();
try self.print_state_neighbour_count();
// Now next state should be set, we go through again to swap
// @TODO Update state
it = self.map.iterator();
while (it.next()) |entry| {
if (entry.value.state == .active) {
if (entry.value.neighbours >= 2 and entry.value.neighbours <= 3) {
entry.value.state = .active;
}
else {
entry.value.state = .inactive;
}
}
else {
if (entry.value.neighbours == 3) {
entry.value.state = .active;
}
}
entry.value.next_state = null;
}
}
pub fn calculate_neighbour_states(self: *Simulation) void {
var it = self.map.iterator();
while (it.next()) |entry| {
var neighbours = entry.value.get_neighbour_positions();
var n_active : u64 = 0;
std.log.warn("Checking neighbours for ({}, {}, {})",
.{entry.value.pos[0], entry.value.pos[1], entry.value.pos[2]});
for (neighbours) |n_pos| {
if (self.map.get(n_pos)) |n| {
if (n.state == .active) {
std.log.warn("Neighbour at ({}, {}, {}) is active",
.{n.pos[0], n.pos[1], n.pos[2]});
n_active += 1;
}
}
else {
// Noop, another function creates our neighbours, and it should
// be run before this one.
}
}
entry.value.neighbours = n_active;
}
}
pub fn count_active_cubes(self: *Simulation) u64 {
var it = self.map.iterator();
var count : u64 = 0;
while (it.next()) |entry| {
if (entry.value.state == .active) {
count += 1;
}
}
return count;
}
};
// Part 2, is just more tedious
const Cube4D = struct {
pos: [4]i64,
state: CubeState = .inactive,
neighbours: u64 = 0,
pub fn get_neighbour_positions(self: *Cube4D, a: *std.mem.Allocator) [80][4]i64 {
var neighbours : [80][4]i64 = undefined;
var x = self.pos[0]-1;
var idx : usize = 0;
while (x <= self.pos[0]+1) : (x += 1) {
var y = self.pos[1]-1;
while (y <= self.pos[1]+1) : (y +=1) {
var z = self.pos[2]-1;
while (z <= self.pos[2]+1) : (z +=1) {
var w = self.pos[3]-1;
while (w <= self.pos[3]+1) : (w+=1) {
if (x == self.pos[0] and y == self.pos[1] and z == self.pos[2]
and w == self.pos[3]) {
continue;
}
neighbours[idx] = [_]i64{x, y, z, w};
idx += 1;
}
}
}
}
return neighbours;
}
};
const Simulation4D = struct {
map: std.hash_map.AutoHashMap([4]i64, Cube4D),
allocator: *std.mem.Allocator,
pub fn init(allocator: *std.mem.Allocator) !*Simulation4D {
var self = try allocator.create(Simulation4D);
errdefer allocator.destroy(self);
self.* = Simulation4D {
.map = std.hash_map.AutoHashMap([4]i64, Cube4D).init(allocator),
.allocator = allocator,
};
return self;
}
pub fn deinit(self: *Simulation4D) void {
self.map.deinit();
self.allocator.destroy(self);
}
pub fn add_cube(self: *Simulation4D, pos: [4]i64, state: CubeState) !void {
try self.map.put(pos, Cube4D {.pos = pos, .state = state});
}
// pub fn print_state(self: *Simulation) !void {
// // @TODO Organize the output somehow
// var stdout = std.io.getStdOut().writer();
// var z_min : i64 = std.math.maxInt(i64);
// var z_max : i64 = std.math.minInt(i64);
// var y_min : i64 = std.math.maxInt(i64);
// var y_max : i64 = std.math.minInt(i64);
// var x_min : i64 = std.math.maxInt(i64);
// var x_max : i64 = std.math.minInt(i64);
// var it = self.map.iterator();
// while (it.next()) |entry| {
// //try entry.value.print_state();
// x_max = std.math.max(x_max, entry.value.pos[0]);
// x_min = std.math.min(x_min, entry.value.pos[0]);
// y_max = std.math.max(y_max, entry.value.pos[1]);
// y_min = std.math.min(y_min, entry.value.pos[1]);
// z_max = std.math.max(z_max, entry.value.pos[2]);
// z_min = std.math.min(z_min, entry.value.pos[2]);
// }
// // For from lowest to highest then
// var z_pos = z_min;
// while (z_pos <= z_max) : (z_pos += 1) {
// try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max});
// var y_pos = y_min;
// while (y_pos <= y_max) : (y_pos += 1) {
// var x_pos = x_min;
// while (x_pos <= x_max) : (x_pos += 1) {
// //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos});
// var c : u8 = '.';
// if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| {
// if (cube.state == .active) {
// c = '#';
// }
// }
// try stdout.print("{c}", .{c});
// }
// _ = try stdout.write("\n");
// }
// _ = try stdout.write("\n");
// }
// _ = try stdout.write("\n");
// }
// pub fn print_state_neighbour_count (self: *Simulation) !void {
// // @TODO Organize the output somehow
// var stdout = std.io.getStdOut().writer();
// var z_min : i64 = std.math.maxInt(i64);
// var z_max : i64 = std.math.minInt(i64);
// var y_min : i64 = std.math.maxInt(i64);
// var y_max : i64 = std.math.minInt(i64);
// var x_min : i64 = std.math.maxInt(i64);
// var x_max : i64 = std.math.minInt(i64);
// var it = self.map.iterator();
// while (it.next()) |entry| {
// //try entry.value.print_state();
// x_max = std.math.max(x_max, entry.value.pos[0]);
// x_min = std.math.min(x_min, entry.value.pos[0]);
// y_max = std.math.max(y_max, entry.value.pos[1]);
// y_min = std.math.min(y_min, entry.value.pos[1]);
// z_max = std.math.max(z_max, entry.value.pos[2]);
// z_min = std.math.min(z_min, entry.value.pos[2]);
// }
// // For from lowest to highest then
// var z_pos = z_min;
// while (z_pos <= z_max) : (z_pos += 1) {
// try stdout.print("z={} x=[{}..{}],y=[{}..{}]\n", .{z_pos, x_min, x_max, y_min, y_max});
// var y_pos = y_min;
// while (y_pos <= y_max) : (y_pos += 1) {
// var x_pos = x_min;
// while (x_pos <= x_max) : (x_pos += 1) {
// //try stdout.print("({}, {}, {})\n", .{x_pos, y_pos, z_pos});
// var c : u64 = 0;
// if (self.map.get([_]i64{x_pos, y_pos, z_pos})) |cube| {
// c = cube.neighbours;
// }
// try stdout.print("{:02}", .{c});
// }
// _ = try stdout.write("\n");
// }
// _ = try stdout.write("\n");
// }
// _ = try stdout.write("\n");
// }
pub fn do_round(self: *Simulation4D) !void {
// For each cube, check all of it's neighbours
var it = self.map.iterator();
var neighbours_to_add = std.ArrayList([4]i64).init(self.allocator);
defer neighbours_to_add.deinit();
// Get the neighbours we need possibly create for this round
while (it.next()) |entry| {
var neighbours = entry.value.get_neighbour_positions(self.allocator);
//defer self.allocator.free(neighbours);
var n_active : u64 = 0;
for (neighbours) |n_pos| {
if (self.map.get(n_pos)) |n| {
// Noop, we already have a cube
}
else {
// The neighbour doesn't yet exist, we should create it
// but we don't want to do that while iterating over
// the current entries.
// We also want to make sure that we're not adding a
// value that's already in the list to be added.
// For the moment, we'll check that when iterating
// over neighbours_to_add.
try neighbours_to_add.append(n_pos);
}
}
}
// Add any new neighbours
for (neighbours_to_add.items) |new_pos| {
// Double-check we don't already have this neighbour
if (self.map.get(new_pos)) |v| {
continue;
}
else {
try self.map.putNoClobber(new_pos, Cube4D {.pos = new_pos, .state = .inactive});
}
}
self.calculate_neighbour_states();
// Now next state should be set, we go through again to swap
// @TODO Update state
it = self.map.iterator();
while (it.next()) |entry| {
if (entry.value.state == .active) {
if (entry.value.neighbours >= 2 and entry.value.neighbours <= 3) {
entry.value.state = .active;
}
else {
entry.value.state = .inactive;
}
}
else {
if (entry.value.neighbours == 3) {
entry.value.state = .active;
}
}
}
}
pub fn calculate_neighbour_states(self: *Simulation4D) void {
var it = self.map.iterator();
while (it.next()) |entry| {
var neighbours = entry.value.get_neighbour_positions(self.allocator);
//defer self.allocator.free(neighbours);
var n_active : u64 = 0;
//std.log.warn("Checking neighbours for ({}, {}, {})",
// .{entry.value.pos[0], entry.value.pos[1], entry.value.pos[2]});
for (neighbours) |n_pos| {
if (self.map.get(n_pos)) |n| {
if (n.state == .active) {
//std.log.warn("Neighbour at ({}, {}, {}) is active",
// .{n.pos[0], n.pos[1], n.pos[2]});
n_active += 1;
}
}
else {
// Noop, another function creates our neighbours, and it should
// be run before this one.
}
}
entry.value.neighbours = n_active;
}
}
pub fn count_active_cubes(self: *Simulation4D) u64 {
var it = self.map.iterator();
var count : u64 = 0;
while (it.next()) |entry| {
if (entry.value.state == .active) {
count += 1;
}
}
return count;
}
};
test "get_neighbour_positions" {
var cube = Cube{
.pos = [_]i64{0, 0, 0},
};
var neighbours = cube.get_neighbour_positions();
for (neighbours) |n| {
std.log.warn("({}, {}, {})", .{n[0], n[1], n[2]});
}
}
test "small_cubeway" {
var sim = try Simulation.init(std.testing.allocator);
defer sim.deinit();
try sim.add_cube([_]i64 {0, 0, 0}, .inactive);
try sim.add_cube([_]i64 {1, 0, 0}, .active);
try sim.add_cube([_]i64 {2, 0, 0}, .inactive);
try sim.add_cube([_]i64 {0, 1, 0}, .inactive);
try sim.add_cube([_]i64 {1, 1, 0}, .inactive);
try sim.add_cube([_]i64 {2, 1, 0}, .active);
try sim.add_cube([_]i64 {0, 2, 0}, .active);
try sim.add_cube([_]i64 {1, 2, 0}, .active);
try sim.add_cube([_]i64 {2, 2, 0}, .active);
try sim.print_state();
sim.calculate_neighbour_states();
try sim.print_state_neighbour_count();
std.testing.expectEqual(@as(u64, 5), sim.count_active_cubes());
std.log.warn("\n\n --- First round --- \n", .{});
try sim.do_round(); // 1
try sim.print_state();
std.testing.expectEqual(@as(u64, 11), sim.count_active_cubes());
try sim.do_round(); // 2
try sim.do_round(); // 3
try sim.do_round(); // 4
try sim.do_round(); // 5
try sim.do_round(); // 6
var active = sim.count_active_cubes();
std.testing.expectEqual(@as(u64, 112), active);
}
test "4d_cubeway" {
var sim = try Simulation4D.init(std.testing.allocator);
defer sim.deinit();
try sim.add_cube([_]i64 {0, 0, 0, 0}, .inactive);
try sim.add_cube([_]i64 {1, 0, 0, 0}, .active);
try sim.add_cube([_]i64 {2, 0, 0, 0}, .inactive);
try sim.add_cube([_]i64 {0, 1, 0, 0}, .inactive);
try sim.add_cube([_]i64 {1, 1, 0, 0}, .inactive);
try sim.add_cube([_]i64 {2, 1, 0, 0}, .active);
try sim.add_cube([_]i64 {0, 2, 0, 0}, .active);
try sim.add_cube([_]i64 {1, 2, 0, 0}, .active);
try sim.add_cube([_]i64 {2, 2, 0, 0}, .active);
try sim.do_round(); // 1
try sim.do_round(); // 2
try sim.do_round(); // 3
try sim.do_round(); // 4
try sim.do_round(); // 5
try sim.do_round(); // 6
var active = sim.count_active_cubes();
std.testing.expectEqual(@as(u64, 848), active);
}