Day 14 Part 2

This commit is contained in:
Adam Millerchip 2020-12-15 01:31:10 +09:00
parent facf77e829
commit 1318f63b25
3 changed files with 133 additions and 1 deletions

View File

@ -2,7 +2,7 @@
My (attempted) solutions to [Advent of Code 2020](https://adventofcode.com/2020) in Elixir.
<img width="979" alt="image" src="https://user-images.githubusercontent.com/498229/102020090-57222900-3dba-11eb-9916-4b90c0107dfa.png">
<img width="978" alt="image" src="https://user-images.githubusercontent.com/498229/102170372-8c706900-3ed7-11eb-8bdd-3b0f28b95fde.png">
## Strategy

View File

@ -5,21 +5,64 @@ Day 14 Notes
+--------+
$ elixir day14part1.exs
12135523360904
Thoughts:
Bitmasking.
Not sure if I missed something here. Since the mask bits overwrite the value, we could
just copy the bits. But I decided to implement it with real bitmasking, so I *think*
it is necessary to split the mask into two seperate masks - one containing the 1s which
will be ORed to set the bits, and another containing the 0s will will be ANDed to unset
the bits.
When parsing the file, convert the mask into the and and or masks, then when
executing the parsed instructions, apply the masks as necessary.
+--------+
| Part 2 |
+--------+
$ elixir day14part2.exs
2741969047858
Thoughts:
OK. This requires quite a re-write of part 1. Now the Xs in the mask represent
floating values. We need to permute all the possible floating values, then substitute
those into the result.
I wasted a lot of time misreading the question, by replacing the values in the mask
*before* applying the mask to the memory address. But the question requires the mask
to be applied first, and then replace the values in the masked result. Reworking ensues.
This got a bit long so decided to split the file parsing logic into its own module.
Parser Module
Parses the file, and produces the "mask" along with the indices that contained the X bits.
Can't perform the substitution at this stage, because it has to be applied to the
masked value.
Day14Part2 Module
Runs the main logic. For each mask, applies it to the value, permutes the options
nd subsitutes the relevant bits.
I was unsure whether it was better to compute all the permuted bits ahead of time in the
parsing stage, or do it on demand each time we apply a mask. Decided to do it on demand,
because it keeps the data that is being passed around simpler - the mask can just
store the X indices, rather than all the permuted replacement values.
+------------------+
| Overall Thoughts |
+------------------+
Enjoyed this one. Was great to get the experience of working with binaries as integer
values rather than strings. Getting used to working with the <<>> syntax, although it
took me a long time to work it all out.
Especially for Part 2 - generating permutations by counting in binary was pretty
fun, but completely unnecessary. :-)

89
day14/day14part2.exs Normal file
View File

@ -0,0 +1,89 @@
defmodule Day14Part2 do
use Bitwise
def run do
File.read!("input")
|> String.split("\n", trim: true)
|> Enum.map(&Parser.parse_command/1)
|> Enum.reduce({nil, %{}}, &execute/2)
|> sum_memory()
|> IO.inspect()
end
def execute({:mask, decoded_mask}, {_decoded_mask, mem}), do: {decoded_mask, mem}
def execute({:mem, addr, value}, {decoded_mask, mem}) do
mem =
apply_mask(addr, decoded_mask)
|> Enum.reduce(mem, fn addr, mem -> Map.put(mem, addr, value) end)
{decoded_mask, mem}
end
def apply_mask(addr, {mask, replacement_indices}) do
masked_address = addr ||| mask
replacement_bits = permute_floating_bits(replacement_indices)
Enum.map(replacement_bits, &insert_floating_bits(masked_address, &1))
end
def insert_floating_bits(value, floating_bits) do
Enum.reduce(floating_bits, value, fn {idx, bit}, value ->
replace_bit(value, idx, bit)
end)
end
def replace_bit(number, idx, bit) do
left = idx
right = 35 - idx
<<prefix::size(left), _::1, suffix::size(right)>> = <<number::36>>
<<number::36>> = <<prefix::size(left), bit::1, suffix::size(right)>>
number
end
# Since it's binary day, let's implement permutations in binary too
def permute_floating_bits(indices) do
len = length(indices)
size = :math.pow(2, len) |> trunc
for permutation <- 0..(size - 1) do
bits = for <<(bit::1 <- <<permutation::size(len)>>)>>, do: bit
Enum.zip(indices, bits)
end
end
def sum_memory({_mask, mem}), do: Map.values(mem) |> Enum.sum()
end
defmodule Parser do
def parse_command("mask = " <> mask), do: {:mask, decode_mask(mask)}
def parse_command(mem_command) do
[addr, value] =
mem_command
|> String.split(["mem[", "] = "], trim: true)
|> Enum.map(&String.to_integer/1)
{:mem, addr, value}
end
def decode_mask(mask) when byte_size(mask) == 36 do
<<intmask::36>> = for <<bit::binary-1 <- mask>>, into: <<>>, do: mask_bit(bit)
{intmask, replacement_indices(mask)}
end
def mask_bit("X"), do: <<0::1>>
def mask_bit("1"), do: <<1::1>>
def mask_bit("0"), do: <<0::1>>
# 0-indexed from the MOST significant bit
def replacement_indices(mask) do
mask
|> String.graphemes()
|> Enum.with_index()
|> Enum.filter(&match?({"X", _}, &1))
|> Enum.map(&elem(&1, 1))
end
end
Day14Part2.run()