solve day 12
This commit is contained in:
parent
08222a641e
commit
71d4c1bf17
@ -6,7 +6,6 @@ pub const Input = struct {
|
|||||||
lines: []const []const u8
|
lines: []const []const u8
|
||||||
};
|
};
|
||||||
|
|
||||||
// For now only .uint is needed
|
|
||||||
pub const Result = union(enum) {
|
pub const Result = union(enum) {
|
||||||
uint: u64,
|
uint: u64,
|
||||||
int: i64,
|
int: i64,
|
||||||
|
@ -195,7 +195,7 @@ pub fn part2(input: *aoc.Input) !aoc.Result {
|
|||||||
return .{ .uint = sum_min_distances(galaxies.items) };
|
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("day9.zig")),
|
||||||
create_day(@import("day10.zig")),
|
create_day(@import("day10.zig")),
|
||||||
create_day(@import("day11.zig")),
|
create_day(@import("day11.zig")),
|
||||||
|
create_day(@import("day12.zig")),
|
||||||
};
|
};
|
||||||
|
|
||||||
fn kilobytes(count: u32) u32 {
|
fn kilobytes(count: u32) u32 {
|
||||||
@ -187,4 +188,5 @@ test {
|
|||||||
_ = @import("day9.zig");
|
_ = @import("day9.zig");
|
||||||
_ = @import("day10.zig");
|
_ = @import("day10.zig");
|
||||||
_ = @import("day11.zig");
|
_ = @import("day11.zig");
|
||||||
|
_ = @import("day12.zig");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user