solve day 12

This commit is contained in:
Rokas Puzonas 2024-01-17 01:21:13 +02:00
parent 08222a641e
commit 71d4c1bf17
4 changed files with 365 additions and 2 deletions

View File

@ -6,7 +6,6 @@ pub const Input = struct {
lines: []const []const u8
};
// For now only .uint is needed
pub const Result = union(enum) {
uint: u64,
int: i64,

View File

@ -195,7 +195,7 @@ pub fn part2(input: *aoc.Input) !aoc.Result {
return .{ .uint = sum_min_distances(galaxies.items) };
}
const example_input = [_][]const u8{
const example_input = [_][]u8{
"...#......",
".......#..",
"#.........",

362
src/day12.zig Normal file
View File

@ -0,0 +1,362 @@
const std = @import("std");
const aoc = @import("./aoc.zig");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const SpringState = enum {
Operational, Damaged, Unknown,
fn from_char(char: u8) ?SpringState {
return switch (char) {
'.' => .Operational,
'#' => .Damaged,
'?' => .Unknown,
else => null
};
}
fn to_char(self: SpringState) u8 {
return switch (self) {
.Operational => '.',
.Damaged => '#',
.Unknown => '?',
};
}
};
fn print_states(states: []const SpringState) void {
for (states) |state| {
std.debug.print("{c}", .{state.to_char()});
}
std.debug.print("\n", .{});
}
const InputRow = struct {
states: []SpringState,
damaged_groups: []u32,
fn print(self: InputRow) void {
for (self.states) |state| {
std.debug.print("{c}", .{state.to_char()});
}
std.debug.print(" ", .{});
for (0.., self.damaged_groups) |i, group_size| {
if (i != 0) {
std.debug.print(",", .{});
}
std.debug.print("{}", .{group_size});
}
std.debug.print("\n", .{});
}
};
const Input = struct {
rows: ArrayList(InputRow),
fn init(allocator: Allocator) Input {
return Input{
.rows = ArrayList(InputRow).init(allocator)
};
}
fn deinit(self: Input) void {
const allocator = self.rows.allocator;
for (self.rows.items) |row| {
allocator.free(row.states);
allocator.free(row.damaged_groups);
}
self.rows.deinit();
}
fn print(self: Input) void {
for (self.rows.items) |row| {
row.print();
}
}
};
fn parse_input_row(allocator: Allocator, line: []const u8) !InputRow {
const space_idx = std.mem.indexOfScalar(u8, line, ' ') orelse return error.InvalidFormat;
var states = try allocator.alloc(SpringState, space_idx);
errdefer allocator.free(states);
const commas = std.mem.count(u8, line, ",");
var damaged_groups = try allocator.alloc(u32, commas + 1);
errdefer allocator.free(damaged_groups);
for (0.., line[0..space_idx]) |i, char| {
states[i] = SpringState.from_char(char) orelse return error.InvalidFormat;
}
var it = std.mem.splitScalar(u8, line[(space_idx+1)..], ',');
var idx: usize = 0;
while (it.next()) |number| {
damaged_groups[idx] = try std.fmt.parseInt(u32, number, 10);
idx += 1;
}
return InputRow{
.states = states,
.damaged_groups = damaged_groups,
};
}
fn parse_input(allocator: Allocator, lines: []const []const u8) !Input {
var parsed = Input.init(allocator);
errdefer parsed.deinit();
for (lines) |line| {
try parsed.rows.append(try parse_input_row(allocator, line));
}
return parsed;
}
fn is_arrangement_valid(states: []const SpringState, groups: []const u32) bool {
var it = std.mem.tokenizeScalar(SpringState, states, .Operational);
var i: usize = 0;
while (it.next()) |group| {
if (groups.len == i) return false;
if (group.len != groups[i]) return false;
i += 1;
}
if (i != groups.len) {
return false;
}
return true;
}
fn count_valid_arrangements(allocator: Allocator, states: []const SpringState, groups: []const u32) !u32 {
var unknown_count: u32 = 0;
for (states) |state| {
if (state == .Unknown) {
unknown_count += 1;
}
}
if (unknown_count == 0) {
if (is_arrangement_valid(states, groups)) {
return 1;
} else {
return 0;
}
}
var unknown_indexes = try allocator.alloc(usize, unknown_count);
defer allocator.free(unknown_indexes);
var is_unknown_damaged = try allocator.alloc(bool, unknown_count);
defer allocator.free(is_unknown_damaged);
@memset(is_unknown_damaged, false);
var i: usize = 0;
for (0.., states) |index, state| {
if (state == .Unknown) {
unknown_indexes[i] = index;
i += 1;
}
}
const arrangement = try allocator.dupe(SpringState, states);
defer allocator.free(arrangement);
@memcpy(arrangement, states);
var count: u32 = 0;
for (0..std.math.pow(u32, 2, unknown_count)) |_| {
for (unknown_indexes, is_unknown_damaged) |index, is_damaged| {
arrangement[index] = if (is_damaged) .Damaged else .Operational;
}
if (is_arrangement_valid(arrangement, groups)) {
count += 1;
}
const carry = is_unknown_damaged[0];
is_unknown_damaged[0] = !is_unknown_damaged[0];
if (carry) {
for (is_unknown_damaged[1..]) |*is_damaged| {
const was_set = is_damaged.*;
is_damaged.* = !is_damaged.*;
if (was_set) break;
}
}
}
return count;
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const parsed = try parse_input(allocator, input.lines);
defer parsed.deinit();
var result: u32 = 0;
for (parsed.rows.items) |row| {
result += try count_valid_arrangements(allocator, row.states, row.damaged_groups);
}
return .{ .uint = result };
}
fn unfold_input_row(allocator: Allocator, input_row: InputRow) !InputRow {
const multiplier = 5;
const state_count = input_row.states.len;
var unfolded_states = try allocator.alloc(SpringState, (state_count + 1) * multiplier - 1);
errdefer allocator.free(unfolded_states);
const group_count = input_row.damaged_groups.len;
var unfolded_groups = try allocator.alloc(u32, group_count * multiplier);
errdefer allocator.free(unfolded_groups);
for (0..multiplier) |i| {
if (i != 0) {
unfolded_states[i*(state_count+1)-1] = .Unknown;
}
@memcpy(unfolded_states[(i*(state_count+1))..(i*(state_count+1) + state_count)], input_row.states);
@memcpy(unfolded_groups[(i*group_count)..((i+1)*group_count)], input_row.damaged_groups);
}
return InputRow{
.states = unfolded_states,
.damaged_groups = unfolded_groups
};
}
fn unfold_input(allocator: Allocator, input: Input) !Input {
var unfolded = Input.init(allocator);
errdefer unfolded.deinit();
for (input.rows.items) |row| {
try unfolded.rows.append(try unfold_input_row(allocator, row));
}
return unfolded;
}
const CacheKey = struct {
states: []const SpringState,
groups: []const u32,
};
const CacheKeyContext = struct {
fn splitU32(value: u64) [8]u8 {
return .{
@truncate(value >> 8*0),
@truncate(value >> 8*1),
@truncate(value >> 8*2),
@truncate(value >> 8*3),
@truncate(value >> 8*4),
@truncate(value >> 8*5),
@truncate(value >> 8*6),
@truncate(value >> 8*7),
};
}
pub fn hash(_: CacheKeyContext, key: CacheKey) u64 {
var h = std.hash.Fnv1a_64.init();
h.update(&splitU32(@intFromPtr(key.states.ptr)));
h.update(&splitU32(@intCast(key.states.len)));
h.update(&splitU32(@intFromPtr(key.groups.ptr)));
h.update(&splitU32(@intCast(key.groups.len)));
return h.final();
}
pub fn eql(_: CacheKeyContext, a: CacheKey, b: CacheKey) bool {
return std.mem.eql(SpringState, a.states, b.states) and std.mem.eql(u32, a.groups, b.groups);
}
};
const Cache = std.HashMap(CacheKey, u64, CacheKeyContext, 80);
fn count_valid_arrangements_dp(cache: *Cache, states: []const SpringState, groups: []const u32) !u64 {
if (states.len == 0) {
if (groups.len == 0) {
// If there are no springs left AND expect no more groups,
// THEN this is a valid arrangment
return 1;
} else {
// If there are no springs left, but expected to have some groups,
// THEN this is not a valid arrangment
return 0;
}
}
if (groups.len == 0) {
if (std.mem.indexOfScalar(SpringState, states, .Damaged) != null) {
// If we expect to see no more groups, but there are still some damanged springs left
// THEN this is not a valid arrangment
return 0;
} else {
// If we expect to see no more groups and we have no more damaged springs
// THEN this arrangment is valid
return 1;
}
}
var cache_key = CacheKey{ .states = states, .groups = groups };
if (cache.get(cache_key)) |cached_result| {
return cached_result;
}
var result: u64 = 0;
if (states[0] == .Operational or states[0] == .Unknown) {
result += try count_valid_arrangements_dp(cache, states[1..], groups);
}
const group_size = groups[0];
if (
(states[0] == .Damaged or states[0] == .Unknown) // If the leading spring is damanged
and (group_size <= states.len) // If the number of springs left is higher than the group size
and (std.mem.indexOfScalar(SpringState, states[0..group_size], .Operational) == null) // If contains continous group of broken springs
) {
// If the damaged group ends with an operational spring
if (group_size == states.len) {
result += try count_valid_arrangements_dp(cache, states[group_size..], groups[1..]);
} else if (states[group_size] != .Damaged) {
result += try count_valid_arrangements_dp(cache, states[(group_size+1)..], groups[1..]);
}
}
try cache.put(cache_key, result);
return result;
}
pub fn part2(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const parsed = try parse_input(allocator, input.lines);
defer parsed.deinit();
const unfolded = try unfold_input(allocator, parsed);
defer unfolded.deinit();
var cache = Cache.init(allocator);
defer cache.deinit();
var result: u64 = 0;
for (unfolded.rows.items) |row| {
result += try count_valid_arrangements_dp(&cache, row.states, row.damaged_groups);
}
return .{ .uint = result };
}
const example_input = [_][]const u8{
"???.### 1,1,3",
".??..??...?##. 1,1,3",
"?#?#?#?#?#?#?#? 1,3,1,6",
"????.#...#... 4,1,1",
"????.######..#####. 1,6,5",
"?###???????? 3,2,1",
};
test "part 1 example" {
try aoc.expectAnswerUInt(part1, 21, &example_input);
}
test "part 2 example" {
try aoc.expectAnswerUInt(part2, 525152, &example_input);
}

View File

@ -32,6 +32,7 @@ const Days = [_]aoc.Day{
create_day(@import("day9.zig")),
create_day(@import("day10.zig")),
create_day(@import("day11.zig")),
create_day(@import("day12.zig")),
};
fn kilobytes(count: u32) u32 {
@ -187,4 +188,5 @@ test {
_ = @import("day9.zig");
_ = @import("day10.zig");
_ = @import("day11.zig");
_ = @import("day12.zig");
}