2023-12-1
Created on Monday, December 23, 2024.
Day 1
Ok, speed running because I'm already 8 days late.
We've got to take strings like '1two3four5' and get first and last digits and turn them into a number. This feels like a job for a regex, if we use the \d digit regular expression, and do a find first and find last, that should get each single digit. We can try first of all seeing if regexes are greedy enough to consume all the letters.
## Import ipytest and get it setup for use in Python Notebook
import pytest
import ipytest
ipytest.autoconfig()
tests = [
("1abc2", 12),
("pqr3stu8vwx", 38),
("a1b2c3d4e5f",15),
("treb7uchet",77)]
import re
def numbers_from_string(s):
matches = re.findall("\d",s)
return int(matches[0]+matches[-1])
for test in tests:
assert numbers_from_string(test[0]) == test[1]
def total(lines):
return sum(map(numbers_from_string, lines))
assert total(map(lambda a: a[0], tests)) == 142
Ok, lets try this on the actual data
lines = [line.strip() for line in open("day1.txt").readlines()]
nums = map(numbers_from_string, lines)
total = sum(nums)
print(total)
54159
Part 2
Oh wow, so we need to look for either a numeral or a digit spelled out as one, two, three, four, five, six, seven, eight or nine.
New test data, and therefore some new tests. Our regex can probably still do this, but the convert to int is going to be complex.
tests = [
("two1nine", 29),
("eightwothree", 83),
("abcone2threexyz",13),
("xtwone3four",24),
("4nineeightseven2",42),
("zoneight234",14),
("7pqrstsixteen",76)]
test_to_number = {
"one":1,
"1":1,
"two":2,
"2":2,
"three":3,
"3":3,
"four":4,
"4":4,
"five":5,
"5":5,
"six":6,
"6":6,
"seven":7,
"7":7,
"eight":8,
"8":8,
"nine":9,
"9":9}
def numbers_from_string(s):
matches = re.findall("one|two|three|four|five|six|seven|eight|nine|\d",s)
return test_to_number[matches[0]]*10+test_to_number[matches[-1]]
for test in tests:
assert numbers_from_string(test[0]) == test[1]
def total(lines):
return sum(map(numbers_from_string, lines))
assert total(map(lambda a: a[0], tests)) == 281
I think there's a problem in here, hinted by one of the examples, but not spelt out. I haven't looked at teh test data in detail, but this is one of those "my spideysense is tingling" situations.
The way regex's work is that they consume the letters, so on the example "eightwothree", the first "eight" is matched, and the regex continues on "wothree" to find "three". But what if the question was "eightwo". Reading the spec, I'd expect that to return 82, whereas I think it would in fact return 8.
Let's fix that with a standalone test before working out our total from the full data
assert numbers_from_string("eightwo") != 82
def numbers_from_string(s):
matches = re.findall("(?=(one|two|three|four|five|six|seven|eight|nine|\d))",s)
return test_to_number[matches[0]]*10+test_to_number[matches[-1]]
assert numbers_from_string("eightwo") == 82
# Check we didn't break any of our examples
for test in tests:
assert numbers_from_string(test[0]) == test[1]
Ok, lets do this on the data again
lines = [line.strip() for line in open("day1.txt").readlines()]
nums = map(numbers_from_string, lines)
total = sum(nums)
print(total)
53866
Next
Previous