-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathday-2.py
executable file
·187 lines (141 loc) · 5.16 KB
/
day-2.py
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python
"""Advent of Code Programming Puzzles
2019 Edition - Day Two
Puzzle Solution in Python
"""
import argparse
import logging
import sys
EXIT_SUCCESS = 0
LOG_FORMAT = ('%(asctime)s - %(levelname)s - %(module)s - '
'%(funcName)s - %(message)s')
log = logging.getLogger(__name__)
# Common Methods ---------------------------------------------------------------
ADD = 1
MUL = 2
HALT = 99
def load_contents(filename: str) -> list[int]:
"""Load contents from the given file
:param filename: filename as string
:return: list of integers
"""
contents = list(map(int, open(filename).read().strip().split(',')))
log.info(f'Loaded {len(contents)} values from {filename}')
return contents
def patch(program: list[int], noun: int, verb: int) -> list[int]:
"""Restore the gravity assist program
:param program: Intcode program
:param noun: updated noun value
:param verb: updated verb value
:return: patched Intcode program
"""
program[1] = noun
program[2] = verb
return program
def execute_program(contents: list[int], noun: int, verb: int):
"""Solve part one of the puzzle
:param contents: list of integers
:return: answer for the part one of the puzzle
"""
def execute(instruction: int, operand_a: int, operand_b: int) -> int:
"""Evaluate operands using the given arithmetic instruction
:param instruction: Intcode arithmetic instruction
:param operand_a: first operand
:param operand_b: second operand
:return: result of the arithmetical instruction
"""
if instruction == ADD:
return operand_a + operand_b
if instruction == MUL:
return operand_a * operand_b
program = patch(program=contents.copy(), noun=noun, verb=verb)
pc = 0
instr = program[pc]
while instr in [ADD, MUL]:
a_ptr, b_ptr, r_ptr = program[pc + 1:pc + 4]
a, b = [program[ptr] for ptr in [a_ptr, b_ptr]]
program[r_ptr] = execute(instruction=instr, operand_a=program[a_ptr],
operand_b=program[b_ptr])
log.debug(f'pc: {pc:02x}, instr: {instr:02d}, a_ptr:{a_ptr}, a:{a}, '
f'b_ptr: {b_ptr}, b: {b}')
pc += 4
instr = program[pc]
answer = program[0]
return answer
# Puzzle Solving Methods -------------------------------------------------------
def solve(contents: list[int]) -> int:
"""Solve part one of the puzzle
:param contents: list of integers
:return: answer for the part one of the puzzle
"""
input_program = len(contents) > 12
if input_program:
noun = 12
verb = 2
else:
noun = 9
verb = 10
answer = execute_program(contents=contents, noun=noun, verb=verb)
return answer
REQUESTED_OUTPUT = 19690720
def solve_part_two(contents: list[int]) -> int:
"""Solve part two of the puzzle
:param contents: list of integers
:return: answer for the part one of the puzzle
"""
input_program = len(contents) > 12
upper_bound = 100 if input_program else len(contents)
for noun in range(upper_bound):
for verb in range(upper_bound):
first_position = execute_program(
contents=contents, noun=noun, verb=verb)
if REQUESTED_OUTPUT == first_position:
log.info(f'noun: {noun}, verb: {verb}')
answer = 100 * noun + verb
return answer
# Support Methods --------------------------------------------------------------
def configure_logger(verbose: bool):
"""Configure logging
:param verbose: display debug and info messages
:return: nothing
"""
logger = logging.getLogger()
logger.handlers = []
stdout = logging.StreamHandler(sys.stdout)
stdout.setLevel(level=logging.WARNING)
stdout.setFormatter(logging.Formatter(LOG_FORMAT))
logger.addHandler(stdout)
if verbose:
stdout.setLevel(level=logging.DEBUG)
logger.setLevel(level=logging.DEBUG)
def parse_arguments() -> argparse.Namespace:
"""Parse arguments provided by the command-line
:return: list of decoded arguments
"""
parser = argparse.ArgumentParser(description=__doc__)
pa = parser.add_argument
pa('filename', type=str, help='input contents filename')
pa('-p', '--part', type=int, help='solve only the given part')
pa('-v', '--verbose', action='store_true', help='print extra messages')
arguments = parser.parse_args()
return arguments
def main() -> int:
"""Script main method
:return: script exit code returned to the shell
"""
args = parse_arguments()
configure_logger(verbose=args.verbose)
log.debug(f'Arguments: {args}')
compute_part_one = not args.part or 1 == args.part
compute_part_two = not args.part or 2 == args.part
if compute_part_one:
contents = load_contents(filename=args.filename)
answer = solve(contents=contents)
print(answer)
if compute_part_two:
contents = load_contents(filename=args.filename)
answer = solve_part_two(contents=contents)
print(answer)
return EXIT_SUCCESS
if __name__ == "__main__":
sys.exit(main())