-
Notifications
You must be signed in to change notification settings - Fork 3
/
day08.ex
141 lines (117 loc) · 3.89 KB
/
day08.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
defmodule Elixir2020.Day08.Computer do
alias Elixir2020.Day08.Computer
defstruct [
:program,
:pc,
:pc_seen,
:pc_max,
:acc
]
## new/1: Given a program, create a new computer.
def new(program) when is_list(program) do
pc_max = length(program) - 1
## Turn program into a map with the instruction numbers as keys
## I'd like to use {:array, github: "jfacorro/elixir-array"}, but I can't since
## it requires elixir 1.11.1, and arch only has .0 right now..
## Cannot use list because it's O(n) to access inside of it.
program = program |> Enum.with_index(0) |> Enum.map(fn {k, v} -> {v, k} end) |> Map.new()
%Computer{
program: program,
pc_max: pc_max,
pc: 0,
pc_seen: MapSet.new(),
acc: 0
}
end
## execute/1: Given a computer, run the program until either a termination or infinite loop is detected.
def execute(%Computer{pc: pc, pc_seen: pc_seen, pc_max: pc_max, acc: acc} = c) do
cond do
pc >= pc_max ->
{:halted, acc}
MapSet.member?(pc_seen, pc) ->
{:infinite_loop, acc}
true ->
c |> step() |> execute()
end
end
## step/1: Given a computer, run one instruction.
def step(%Computer{pc: pc, program: program} = c) do
{op, val} = program |> Map.get(pc)
c
|> remember_pc()
|> do_step({op, val})
end
## remember_pc/1: Given a computer, put the current program counter (pc) in the pc_seen set.
def remember_pc(%Computer{pc: pc, pc_seen: pc_seen} = c) do
%Computer{c | pc_seen: pc_seen |> MapSet.put(pc)}
end
## do_step/2 (cpu, {"nop", _}): Advance the PC by one.
def do_step(%Computer{pc: pc} = c, {"nop", _}) do
%Computer{c | pc: pc + 1}
end
## do_step/2 (cpu, {"acc", val}): Advance the PC by one and the accumulator by val.
def do_step(%Computer{pc: pc, acc: acc} = c, {"acc", val}) do
%Computer{c | pc: pc + 1, acc: acc + val}
end
## do_step/2 (cpu, {"jmp", val}): Advance the PC by val.
def do_step(%Computer{pc: pc} = c, {"jmp", val}) do
%Computer{c | pc: pc + val}
end
## find_corruption/1: Look for which instruction needs to be flipped to stop infinite loops
## from occuring, then return the "acc" value after terminating.
def find_corruption(%Computer{} = c) do
case flippable?(c) do
false ->
c |> step() |> find_corruption()
true ->
## Branch and try flipped computer
{state, acc} = c |> flip() |> execute()
if state == :halted do
## It halted, we're done!
acc
else
## Continue on as normal and try flipping the next
## flippable.
c |> step() |> find_corruption()
end
end
end
## flippable?/1 Given a computer, is the next operation to be run flippable?
def flippable?(%Computer{pc: pc, program: program}) do
{op, _} = program |> Map.get(pc)
flip_op(op) != nil
end
## flip/1 Given a computer with the next instruction set to "jmp" or "nop",
## return that computer an updated program that flips that instruction (jmp becomes nop,
## nop becomes jmp.)
def flip(%Computer{pc: pc, program: program} = c) do
{op, val} = program |> Map.get(pc)
%Computer{c | program: Map.put(program, pc, {flip_op(op), val})}
end
## flip_op/1 Given an op that can be flipped, return the flipped version.
## Given any other op, return nil.
def flip_op("jmp"), do: "nop"
def flip_op("nop"), do: "jmp"
def flip_op(_), do: nil
end
defmodule Elixir2020.Day08 do
alias Elixir2020.Day08.Computer
def parse(filename) do
File.stream!(filename)
|> Stream.map(&String.trim/1)
|> Enum.map(fn x ->
[op, val] = String.split(x)
{op, String.to_integer(val)}
end)
end
def part1(filename) do
parse(filename)
|> Computer.new()
|> Computer.execute()
end
def part2(filename) do
parse(filename)
|> Computer.new()
|> Computer.find_corruption()
end
end