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.
|
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
|
## Strategy
|
||||||
|
|
||||||
|
|
43
day14/README
43
day14/README
|
@ -5,21 +5,64 @@ Day 14 Notes
|
||||||
+--------+
|
+--------+
|
||||||
|
|
||||||
$ elixir day14part1.exs
|
$ elixir day14part1.exs
|
||||||
|
12135523360904
|
||||||
|
|
||||||
Thoughts:
|
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 |
|
| Part 2 |
|
||||||
+--------+
|
+--------+
|
||||||
|
|
||||||
$ elixir day14part2.exs
|
$ elixir day14part2.exs
|
||||||
|
2741969047858
|
||||||
|
|
||||||
Thoughts:
|
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 |
|
| 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