Day 14 Part 2
This commit is contained in:
parent
facf77e829
commit
1318f63b25
3 changed files with 133 additions and 1 deletions
|
@ -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
|
||||
|
||||
|
|
43
day14/README
43
day14/README
|
@ -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
89
day14/day14part2.exs
Normal 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()
|
Loading…
Reference in a new issue