483 lines
16 KiB
Zig
483 lines
16 KiB
Zig
const std = @import("std");
|
|
|
|
pub fn main() anyerror!void {
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
var gpa = &arena.allocator;
|
|
|
|
var f = try std.fs.cwd().openFile("input", .{});
|
|
var contents = try f.readToEndAlloc(gpa, std.math.maxInt(u32));
|
|
|
|
var seatmap = try SeatMap.init(gpa);
|
|
var it = std.mem.tokenize(contents, "\n");
|
|
while (it.next()) |line| {
|
|
try seatmap.add_line(line);
|
|
}
|
|
|
|
var changed : usize = 0;
|
|
changed = try seatmap.process_round();
|
|
while (changed != 0) {
|
|
//changed = try seatmap.process_round();
|
|
changed = try seatmap.process_round_part2();
|
|
}
|
|
std.log.info("{} occupied seats", .{seatmap.count_occupied()});
|
|
}
|
|
|
|
const NeighbourState = struct {
|
|
count: usize = 0,
|
|
occupied: usize = 0,
|
|
free: usize = 0,
|
|
};
|
|
|
|
const SeatMap = struct {
|
|
allocator: *std.mem.Allocator,
|
|
data: std.ArrayList(u8),
|
|
width: usize = 0,
|
|
height: usize = 0,
|
|
|
|
pub fn init(a: *std.mem.Allocator) !*SeatMap {
|
|
var self = try a.create(SeatMap);
|
|
errdefer a.destroy(self);
|
|
|
|
// if we use self.* = .{}; then we don't forget
|
|
// obligatory fields
|
|
// also, when using self.* = .{} the fields with
|
|
// defaults are properly initialized!!! it is
|
|
// an error to not use it.
|
|
self.* = .{
|
|
.allocator = a,
|
|
.data = std.ArrayList(u8).init(a),
|
|
};
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *SeatMap) void {
|
|
self.data.deinit();
|
|
self.allocator.destroy(self);
|
|
}
|
|
|
|
pub fn count_occupied(self: *SeatMap) u64 {
|
|
var c: u64 = 0;
|
|
for (self.data.items) |i| {
|
|
if (i == '#') {
|
|
c += 1;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
pub fn print(self: *SeatMap) !void {
|
|
var stdout = std.io.getStdOut();
|
|
var i : usize = 0;
|
|
while (i < self.height) : (i += 1) {
|
|
_ = try stdout.write(self.get_row(i));
|
|
_ = try stdout.write("\n");
|
|
}
|
|
}
|
|
|
|
pub fn get_row(self: *SeatMap, row: usize) []u8 {
|
|
return self.data.items[self.width*row..(self.width*row)+self.width];
|
|
}
|
|
|
|
pub fn add_line(self: *SeatMap, line: []const u8) !void {
|
|
if (self.width != 0) {
|
|
std.debug.assert(line.len == self.width);
|
|
}
|
|
else {
|
|
self.width = line.len;
|
|
}
|
|
try self.data.appendSlice(line[0..]);
|
|
self.height += 1;
|
|
}
|
|
|
|
// returns the number of seats changed
|
|
pub fn process_round(self: *SeatMap) !u64 {
|
|
// First, since operations happen concurrently,
|
|
// we mark all of our changes to a temporary buffer
|
|
var buffer = try self.allocator.alloc(u8, self.data.items.len);
|
|
defer self.allocator.free(buffer);
|
|
|
|
// Iterate above (row, col) pairs
|
|
var row : usize = 0;
|
|
var n_changed : u64 = 0;
|
|
while (row < self.height) : (row += 1) {
|
|
var col : usize = 0;
|
|
while (col < self.width) : (col += 1) {
|
|
if (self.index_of(row, col)) |i| {
|
|
buffer[i] = self.get_new_seat_state(row, col, i);
|
|
if (buffer[i] != self.data.items[i]) {
|
|
n_changed += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy buffer to self.data.items
|
|
std.mem.copy(u8, self.data.items, buffer);
|
|
return n_changed;
|
|
}
|
|
|
|
pub fn get_new_seat_state(self: *SeatMap, row: usize, col: usize, index: usize) u8 {
|
|
// Floor remains unchanged
|
|
var c = self.data.items[index];
|
|
var new_c : u8 = undefined;
|
|
if (c == '.') {
|
|
new_c = '.';
|
|
}
|
|
|
|
// this gives us a struct w/ .count, .occupied, .free
|
|
var neighbour_state = self.get_neighbour_state(row, col);
|
|
if (c == 'L') {
|
|
if (neighbour_state.occupied == 0) {
|
|
new_c = '#';
|
|
}
|
|
else {
|
|
new_c = c;
|
|
}
|
|
}
|
|
if (c == '#') {
|
|
if (neighbour_state.occupied >= 4) {
|
|
new_c = 'L';
|
|
}
|
|
else {
|
|
new_c = c;
|
|
}
|
|
}
|
|
//std.log.debug("({},{}) was '{c}' and will be '{c}'. {} neighbors, {} occupied",
|
|
// .{row, col, c, new_c, neighbour_state.count, neighbour_state.occupied});
|
|
return new_c;
|
|
}
|
|
|
|
pub fn get_neighbour_state(self: *SeatMap, row: usize, col: usize) NeighbourState {
|
|
var r: usize = switch(row) {
|
|
0 => row,
|
|
else => row - 1,
|
|
};
|
|
var count: usize = 0;
|
|
var free: usize = 0;
|
|
var occupied: usize = 0;
|
|
while (r <= row+1) : (r += 1) {
|
|
var c: usize = switch(col) {
|
|
0 => col,
|
|
else => col - 1,
|
|
};
|
|
while (c <= col+1) : (c += 1) {
|
|
if (row == r and col == c) {
|
|
continue;
|
|
}
|
|
if (self.index_of(r, c)) |i| {
|
|
if (self.data.items[i] == '.') {
|
|
// Floor spots don't contribute to neighbour,
|
|
// free, or occupied counts.
|
|
continue;
|
|
}
|
|
count += 1;
|
|
if (self.data.items[i] == '#') {
|
|
occupied += 1;
|
|
}
|
|
else {
|
|
free += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NeighbourState {
|
|
.count = count,
|
|
.free = free,
|
|
.occupied = occupied,
|
|
};
|
|
}
|
|
|
|
// returns the number of seats changed
|
|
pub fn process_round_part2(self: *SeatMap) !u64 {
|
|
// First, since operations happen concurrently,
|
|
// we mark all of our changes to a temporary buffer
|
|
var buffer = try self.allocator.alloc(u8, self.data.items.len);
|
|
defer self.allocator.free(buffer);
|
|
|
|
// Iterate above (row, col) pairs
|
|
var row : usize = 0;
|
|
var n_changed : u64 = 0;
|
|
while (row < self.height) : (row += 1) {
|
|
var col : usize = 0;
|
|
while (col < self.width) : (col += 1) {
|
|
if (self.index_of(row, col)) |i| {
|
|
buffer[i] = self.get_new_seat_state_round2(row, col, i);
|
|
if (buffer[i] != self.data.items[i]) {
|
|
n_changed += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy buffer to self.data.items
|
|
std.mem.copy(u8, self.data.items, buffer);
|
|
return n_changed;
|
|
}
|
|
|
|
pub fn get_new_seat_state_round2(self: *SeatMap, row: usize, col: usize, index: usize) u8 {
|
|
// Floor remains unchanged
|
|
var c = self.data.items[index];
|
|
var new_c : u8 = undefined;
|
|
if (c == '.') {
|
|
new_c = '.';
|
|
}
|
|
|
|
// this gives us a struct w/ .count, .occupied, .free
|
|
var neighbour_state = self.get_neighbour_state_round2(row, col);
|
|
if (c == 'L') {
|
|
if (neighbour_state.occupied == 0) {
|
|
new_c = '#';
|
|
}
|
|
else {
|
|
new_c = c;
|
|
}
|
|
}
|
|
if (c == '#') {
|
|
if (neighbour_state.occupied >= 5) {
|
|
new_c = 'L';
|
|
}
|
|
else {
|
|
new_c = c;
|
|
}
|
|
}
|
|
//std.log.debug("({},{}) was '{c}' and will be '{c}'. {} neighbors, {} occupied",
|
|
// .{row, col, c, new_c, neighbour_state.count, neighbour_state.occupied});
|
|
return new_c;
|
|
}
|
|
|
|
pub fn get_neighbour_state_round2(self: *SeatMap, row: usize, col: usize) NeighbourState {
|
|
var count: usize = 0;
|
|
var free: usize = 0;
|
|
var occupied: usize = 0;
|
|
var direction_vectors = [_][2]i64 {
|
|
[_]i64{-1, -1}, // up and left,
|
|
[_]i64{-1, 0}, // left
|
|
[_]i64{-1, 1}, // down and left,
|
|
[_]i64{0, 1}, // down
|
|
[_]i64{1, 1}, // right and down
|
|
[_]i64{1, 0}, // right
|
|
[_]i64{1, -1}, // right and up,
|
|
[_]i64{0, -1}, // up
|
|
};
|
|
var row_signed = @intCast(i64, row);
|
|
var col_signed = @intCast(i64, col);
|
|
for (direction_vectors) |dv| {
|
|
var space_count : i64 = 0;
|
|
var has_neighbour: bool = false;
|
|
var last_neighbour_was_occupied = false;
|
|
while (true) {
|
|
space_count += 1;
|
|
var r : i64 = row_signed + (dv[0]*space_count);
|
|
var c : i64 = col_signed + (dv[1]*space_count);
|
|
if (r < 0 or c < 0) {
|
|
// invalid spot with negative indices
|
|
break;
|
|
}
|
|
if(self.index_of(@intCast(usize, r), @intCast(usize, c))) |index| {
|
|
if (self.data.items[index] == '.') {
|
|
// We see through floor spaces
|
|
continue;
|
|
}
|
|
else {
|
|
has_neighbour = true;
|
|
if (self.data.items[index] == '#') {
|
|
last_neighbour_was_occupied = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// The index was not valid, so we're out of bounds in some way
|
|
break;
|
|
}
|
|
}
|
|
if (has_neighbour) {
|
|
count += 1;
|
|
if (last_neighbour_was_occupied) {
|
|
occupied += 1;
|
|
}
|
|
else {
|
|
free += 1;
|
|
}
|
|
}
|
|
}
|
|
return NeighbourState {
|
|
.count = count,
|
|
.free = free,
|
|
.occupied = occupied,
|
|
};
|
|
}
|
|
|
|
pub fn index_of(self: *SeatMap, row: usize, col: usize) ?usize {
|
|
if (col >= self.width) {
|
|
return null;
|
|
}
|
|
if (row >= self.height) {
|
|
return null;
|
|
}
|
|
var i = (row*self.width) + col;
|
|
if (i < 0 or i >= self.data.items.len) {
|
|
return null;
|
|
}
|
|
return i;
|
|
}
|
|
};
|
|
|
|
test "part_one" {
|
|
var d = [_][]const u8 {
|
|
"L.LL.LL.LL",
|
|
"LLLLLLL.LL",
|
|
"L.L.L..L..",
|
|
"LLLL.LL.LL",
|
|
"L.LL.LL.LL",
|
|
"L.LLLLL.LL",
|
|
"..L.L.....",
|
|
"LLLLLLLLLL",
|
|
"L.LLLLLL.L",
|
|
"L.LLLLL.LL",
|
|
};
|
|
|
|
var print = true;
|
|
var stdout = std.io.getStdOut();
|
|
|
|
var sm = try SeatMap.init(std.testing.allocator);
|
|
defer sm.deinit();
|
|
for (d) |l| {
|
|
try sm.add_line(l[0..]);
|
|
}
|
|
|
|
var states = [_][]const u8 {
|
|
// 0
|
|
"L.LL.LL.LLLLLLLLL.LLL.L.L..L..LLLL.LL.LLL.LL.LL.LLL.LLLLL.LL..L.L.....LLLLLLLLLLL.LLLLLL.LL.LLLLL.LL",
|
|
// 1
|
|
"#.##.##.#########.###.#.#..#..####.##.###.##.##.###.#####.##..#.#.....###########.######.##.#####.##",
|
|
// 2
|
|
"#.LL.L#.###LLLLLL.L#L.L.L..L..#LLL.LL.L##.LL.LL.LL#.LLLL#.##..L.L.....#LLLLLLLL##.LLLLLL.L#.#LLLL.##",
|
|
// 3
|
|
"#.##.L#.###L###LL.L#L.#.#..#..#L##.##.L##.##.LL.LL#.###L#.##..#.#.....#L######L##.LL###L.L#.#L###.##",
|
|
// 4
|
|
"#.#L.L#.###LLL#LL.L#L.L.L..#..#LLL.##.L##.LL.LL.LL#.LL#L#.##..L.L.....#L#LLLL#L##.LLLLLL.L#.#L#L#.##",
|
|
// 5
|
|
"#.#L.L#.###LLL#LL.L#L.#.L..#..#L##.##.L##.#L.LL.LL#.#L#L#.##..L.L.....#L#L##L#L##.LLLLLL.L#.#L#L#.##"
|
|
};
|
|
// Confirm that initial map is correct
|
|
std.testing.expect(std.mem.eql(u8, sm.data.items, states[0]));
|
|
|
|
if (print) {
|
|
_ = try stdout.write("\nInitial map:\n");
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
|
|
var round: u64 = 0;
|
|
var changed = try sm.process_round();
|
|
while (changed != 0) {
|
|
round += 1;
|
|
var matches = std.mem.eql(u8, sm.data.items, states[round]);
|
|
//std.log.warn("Round {} matches expected state: {}", .{round, matches});
|
|
//std.log.warn("len {}: {}", .{sm.data.items.len, sm.data.items});
|
|
//std.log.warn("len {}: {}", .{states[round].len, states[round]});
|
|
//std.log.warn("index of first diff {}",
|
|
// .{std.mem.indexOfDiff(u8, sm.data.items, states[round])});
|
|
std.testing.expect(matches);
|
|
if (print) {
|
|
var title = try std.fmt.allocPrint(std.testing.allocator, "\nRound {}:\n",
|
|
.{round});
|
|
_ = try stdout.write(title);
|
|
std.testing.allocator.free(title);
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
changed = try sm.process_round();
|
|
}
|
|
if (print) {
|
|
_ = try stdout.write("Final state: \n");
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
std.testing.expect(std.mem.eql(u8, sm.data.items, states[round]));
|
|
|
|
var n_occupied = sm.count_occupied();
|
|
std.testing.expectEqual(n_occupied, 37);
|
|
}
|
|
|
|
test "part two" {
|
|
var d = [_][]const u8 {
|
|
"L.LL.LL.LL",
|
|
"LLLLLLL.LL",
|
|
"L.L.L..L..",
|
|
"LLLL.LL.LL",
|
|
"L.LL.LL.LL",
|
|
"L.LLLLL.LL",
|
|
"..L.L.....",
|
|
"LLLLLLLLLL",
|
|
"L.LLLLLL.L",
|
|
"L.LLLLL.LL",
|
|
};
|
|
|
|
var print = true;
|
|
var stdout = std.io.getStdOut();
|
|
|
|
var sm = try SeatMap.init(std.testing.allocator);
|
|
defer sm.deinit();
|
|
for (d) |l| {
|
|
try sm.add_line(l[0..]);
|
|
}
|
|
|
|
var states = [_][]const u8 {
|
|
// 0
|
|
"L.LL.LL.LLLLLLLLL.LLL.L.L..L..LLLL.LL.LLL.LL.LL.LLL.LLLLL.LL..L.L.....LLLLLLLLLLL.LLLLLL.LL.LLLLL.LL",
|
|
// 1
|
|
"#.##.##.#########.###.#.#..#..####.##.###.##.##.###.#####.##..#.#.....###########.######.##.#####.##",
|
|
// 2
|
|
"#.LL.LL.L##LLLLLL.LLL.L.L..L..LLLL.LL.LLL.LL.LL.LLL.LLLLL.LL..L.L.....LLLLLLLLL##.LLLLLL.L#.LLLLL.L#",
|
|
// 3
|
|
"#.L#.##.L##L#####.LLL.#.#..#..##L#.##.###.##.#L.###.#####.#L..#.#.....LLL####LL##.L#####.L#.L####.L#",
|
|
// 4
|
|
"#.L#.L#.L##LLLLLL.LLL.L.L..#..##LL.LL.L#L.LL.LL.L##.LLLLL.LL..L.L.....LLLLLLLLL##.LLLLL#.L#.L#LL#.L#",
|
|
// 5
|
|
"#.L#.L#.L##LLLLLL.LLL.L.L..#..##L#.#L.L#L.L#.#L.L##.L####.LL..#.#.....LLL###LLL##.LLLLL#.L#.L#LL#.L#",
|
|
// 6
|
|
"#.L#.L#.L##LLLLLL.LLL.L.L..#..##L#.#L.L#L.L#.LL.L##.LLLL#.LL..#.L.....LLL###LLL##.LLLLL#.L#.L#LL#.L#",
|
|
};
|
|
// Confirm that initial map is correct
|
|
std.testing.expect(std.mem.eql(u8, sm.data.items, states[0]));
|
|
|
|
if (print) {
|
|
_ = try stdout.write("\nInitial map:\n");
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
|
|
var round: u64 = 0;
|
|
var changed = try sm.process_round();
|
|
while (changed != 0) {
|
|
round += 1;
|
|
var matches = std.mem.eql(u8, sm.data.items, states[round]);
|
|
//std.log.warn("Round {} matches expected state: {}", .{round, matches});
|
|
//std.log.warn("len {}: {}", .{sm.data.items.len, sm.data.items});
|
|
//std.log.warn("len {}: {}", .{states[round].len, states[round]});
|
|
//std.log.warn("index of first diff {}",
|
|
// .{std.mem.indexOfDiff(u8, sm.data.items, states[round])});
|
|
std.testing.expect(matches);
|
|
if (print) {
|
|
var title = try std.fmt.allocPrint(std.testing.allocator, "\nRound {}:\n",
|
|
.{round});
|
|
_ = try stdout.write(title);
|
|
std.testing.allocator.free(title);
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
changed = try sm.process_round_part2();
|
|
}
|
|
if (print) {
|
|
_ = try stdout.write("Final state: \n");
|
|
try sm.print();
|
|
_ = try stdout.write("\n");
|
|
}
|
|
std.testing.expect(std.mem.eql(u8, sm.data.items, states[round]));
|
|
|
|
var n_occupied = sm.count_occupied();
|
|
std.testing.expectEqual(n_occupied, 26);
|
|
}
|