2023 Advent of Code (Day 1)
Trebuchet?!
It’s the end of the year. Once again, the greatest tradition of all the programmers in the world since the past decade. And is also one of the best ways to learn a new language… Advent of Code!
I’ve to be honest here… I never finished it like ever…
But since I decide to learn Python couple of months ago, I think this is a great opportunity to do it in Python. Anyway, hope I won’t give-up again this year (LOL).
Disclaimer: I’m not doing competitive programming, and I’m quite terrible in terms of algorithm questions, plus I’m just using this chance to learn a rather unfamiliar language. The following codes only illustrates my thought process. It gives correct output, but definitely not the most optimal way to solve the problem.
Day 1: Trebuchet?!
Part 1
Given input like…
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
And we need to concatenate the first and the last number in each line and add them all together.
So, the first line will be 12
, then38
, 15
, and 77
. The final answer will be 142
.
This one seems easy enough. My thought is split all the characters in each string, filter it and get a number array like [1, 2]
. Then I can do something like arr[0] * 10 + arr[— 1]
. And just get the summary of them.
file = open('./calibration.txt', 'r')
content = file.read()
def getNum(str):
digits = []
for c in [*str]:
if c.isdigit():
digits.append(int(c))
return digits[0] * 10 + digits[-1]
def sum(arr):
acc = 0
for i in arr:
acc+=getNum(i)
return acc
ans = sum(content.split())
print(ans)
I use the [*str]
to break each character into an array. Then test the character with isdigit()
, if it’s a digit, I’ll convert it as int()
and store in my array. The rest just following my game plan…
Part 2
This is getting a little bit annoying… Same question with a plot twist…
two1nine -> 29
eightwothree -> 83
abcone2threexyz -> 13
xtwone3four -> 24
4nineeightseven2 -> 42
zoneight234 -> 14
7pqrstsixteen -> 76
The annoying part is I can’t just break each character now, my algorithm needs to be able to recognize the numbers in English…
So, again I’ll keep the idea of having a number array. And store the character when loop through an integer. But when encounter an alphabet, I’ll need another loop to check if it’s a number in English form…
file = open('./calibration.txt', 'r')
content = file.read()
nums = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
def getNum(str):
digits = []
for i, c in enumerate(str):
if c.isdigit():
digits.append(int(c))
for j, num in enumerate(nums):
if str[i:].startswith(num):
digits.append(j)
return digits[0] * 10 + digits[-1]
def sum(arr):
acc = 0
for i in arr:
acc+=getNum(i)
return acc
ans = sum(content.split())
print(ans)
Behold! A nested loop!
I’m sure there’s better ways to do this… But that’s what I got now…
Just to make things more interesting, I found some very different solutions that I think it’s worth to reflect on.
import re
str2num = {
"one": "o1e",
"two": "t2o",
"three": "t3e",
"four": "f4r",
"five": "f5e",
"six": "s6x",
"seven": "s7n",
"eight": "e8t",
"nine": "n9e",
}
def replace_words(text):
for k, v in str2num.items():
text = text.replace(k, v)
return text
def calibration(text):
return sum(int(l[0] + l[-1]) for l in re.sub(r"[A-z]", "", text).split("\n"))
print(calibration(replace_words(text)))
I saw there’s quite a few people use the similar technique. It first replaces all the English number with a fiction word that includes its corresponding integer number. And the rest is pretty much the same. It seems like a quite clever solution; it's kind of hacky, but it really plays well with the requirement. If there’s a slightly change in the requirement, it’s probably not going to work.
f = lambda str, dir: min((str[::dir].find(num[::dir])%99, i) for i, num in enumerate(
'1 2 3 4 5 6 7 8 9 one two three four five six seven eight nine'.split()))[1]%9+1
print(sum(10*f(x, 1) + f(x, -1) for x in open('./calibration.txt')))
This one is pretty crazy at first sight, so I have to manually convert it to procedural code before I can actually understand what it actually does…
def f(str, dir):
tuples = []
for i, num in enumerate('1 2 3 4 5 6 7 8 9 one two three four five six seven eight nine'.split()):
found = (str[::dir].find(num[::dir]))%99
tuples.append((found, i))
return min(tuples)[1]%9+1
print(sum(10*f(x, 1) + f(x, -1) for x in open('./calibration.txt')))
The 99
is just a constant represent a long string length, as long as it’s over the original string size it should work. This is the part I don’t really like, because it’s a non-feature-proof way to do it.
When given a collection of tuple
s, Python will automatically compare them with their key. So, the min(tuples)
is similar to min(tuples, key=lambda x:x[0])
. This part is the most confusing one for me. But apparently just my own skill issue.
Once we find the smallest tuple
in the collection, we take the [1]
element in the tuple
, in this case will be the index of our string and number map. And we divided it with 9 to get its reminder, and we will get a 0 based number between 0 to 8. Finally, we plus 1 to get the actual 1 to 9 number.
It’s really cool solution at first glance. But once I can understand what it does, the magic kind of fade away. And I don’t feel like it’s a good solution anymore… IDK, maybe it’s just me.
That’s it. The first day of 2023 Advent of Code!