solve day 12
This commit is contained in:
parent
08222a641e
commit
71d4c1bf17
@ -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,
|
||||
|
@ -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
362
src/day12.zig
Normal 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);
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user