-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcalibration.py
131 lines (102 loc) · 2.77 KB
/
calibration.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
import sys
import re
from typing import Generator
### Part 1 ###
def part_one(source) -> int:
# - Reads each line of input
# - Adds the first and last numbers
# - returns the total sum
total = 0
for line in source:
line = line.strip()
# Empty line
if not line: break
# Find the index of the first digit
first = None
for c in line:
if c.isnumeric():
first = c
break
# Find the index of the last digit
last = None
for c in line[::-1]:
if c.isnumeric():
last = c
break
if first is None or last is None:
raise ValueError("No digits found in the line!")
total += int(f"{first}{last}")
return total
### Part 2 ###
LOW_NUMBERS = {
'one' : '1',
'two' : '2',
'three' : '3',
'four' : '4',
'five' : '5',
'six' : '6',
'seven' : '7',
'eight' : '8',
'nine' : '9',
}
def get_num_words(line:str) -> list[tuple[str, iter]]:
'''Searches the string for all number words, in order,
returns a tuple with (word, start index)'''
# Uses lookahead to allow capturing overlapping words,
# like "eightwo" as both 8 and 2.
num_regex = re.compile(fr"(?=({'|'.join(LOW_NUMBERS.keys())}))")
result = []
for mat in re.finditer(num_regex, line):
# Tuple of (word, start index)
result.append((mat.group(1), mat.span()[1]))
return result
def part_two(source) -> int:
# - Reads each line of input
# - Scans the numbers words
# - Selects either first/last digit or word
# - Adds the first and last numbers
# - returns the total sum
total = 0
for line in source:
line = line.strip()
# Empty line
if not line: break
words = get_num_words(line)
# Find the index of the first digit
first = None
for i, c in enumerate(line):
if c.isnumeric():
first = i
break
# Find the index of the last digit
last = None
for i, c in enumerate(line[::-1]):
if c.isnumeric():
last = len(line) - i - 1
break
# Set digit values based on whether the word or the literal digit occurs first
if words:
first = line[first] if first < words[0][1] else LOW_NUMBERS[words[0][0]]
last = line[last] if last > words[-1][1] else LOW_NUMBERS[words[-1][0]]
else:
first = line[first]
last = line[last]
if first is None or last is None:
raise ValueError("No digits found in the line!")
total += int(f"{first}{last}")
return total
if __name__ == "__main__":
if len(sys.argv) <= 1 or sys.argv[1] == '-':
print("Reading from stdin.")
text = [ line for line in sys.stdin ]
else:
try:
print(f"Reading from {sys.argv[1]}.")
with open(sys.argv[1]) as f:
text = [ line for line in sys.stdin ]
except FileNotFoundError:
print(f"File {sys.argv[1]} not found!")
exit()
text = list(line for line in sys.stdin)
print(f"Part 1: {part_one(text)}")
print(f"Part 2: {part_two(text)}")