[Day 1] Trebuchet?! // Advent of Code 2023 (Python)
Solving Advent of Code 2023 Day 1 in Python: the first and last digit of each line, then spelled out digits and overlaps like eightwothree.

Day 1: Trebuchet?! (via DALL-E 3)
Advent of Code
Something is wrong with global snow production, and you’ve been selected to take a look. The Elves have even given you…adventofcode.com
Part One
# --- Day 1: Trebuchet?! ---
## --- Part One ---
Something is wrong with global snow production, and you've been selected to take a look. The Elves have even given you a map; on it, they've used stars to mark the top fifty locations that are likely to be having problems.
You've been doing this long enough to know that to restore snow operations, you need to check all fifty stars by December 25th.
Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants one star. Good luck!
You try to ask why they can't just use a weather machine ("not powerful enough") and where they're even sending you ("the sky") and why your map looks mostly blank ("you sure ask a lot of questions") and hang on did you just say the sky ("of course, where do you think snow comes from") when you realize that the Elves are already loading you into a trebuchet ("please hold still, we need to strap you in").
As they're making the final adjustments, they discover that their calibration document (your puzzle input) has been **amended** by a very young Elf who was apparently just excited to show off her art skills. Consequently, the Elves are having trouble reading the values on the document.
The newly-improved calibration document consists of lines of text; each line originally contained a specific calibration value that the Elves now need to recover. On each line, the **calibration value** can be found by combining the **first digit** and the **last digit** (in that order) to form a single two-digit number.
For example:
```text
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
```
In this example, the calibration values of these four lines are 12, 38, 15, and 77. Adding these together produces 142.
Consider your entire calibration document. What is the sum of all of the calibration values?
➡️ The core of the problem revolves around processing a list of strings, each of which has been modified in a way that obscures the original data.
💭The core task is to extract calibration value by combining the first digit and the last digit (in that order) to form a single two-digit number from each line and then perform a calculation summing all these values to get the answer.
So, I quickly whipped up some code to test the example, wrote a simple test case:

💡 Edge case: In case of “treb7uchet”, we only have a single digit — “7”, so in such cases, we need to ensure that last = first.

Once the code was working as intended, we can just load up the input.txt in main() and ran our method sum_calibration_value(…) to get the answer for part 1.
# --- Day 1: Trebuchet?! ---
# --- Part one ---
def get_combined_digits(line):
# extract all digits from the input line
digits = [ch for ch in line if ch.isdigit()]
# handle cases with no digits found
if not digits:
return 0 # or handle as per specific requirements
# extract the first and last digits, duplicate if only one digit present
first, last = digits[0], digits[-1] if len(digits) > 1 else digits[0]
# return the combined integer value
return int(first + last)
def sum_calibration_values(lines):
# sum up combined digits values for each line
return sum(get_combined_digits(line.strip()) for line in lines)
def main():
# read lines from the file
with open("input.txt") as f:
lines = f.readlines()
# calculate and display the sum of calibration values
print(sum_calibration_values(lines))
def test_calibration_values():
# define test cases with expected outcomes
test_cases = [
("1abc2", 12),
("pqr3stu8vwx", 38),
("a1b2c3d4e5f", 15),
("treb7uchet", 77),
("no digits here", 0),
]
# test get_combined_digits function
for line, expected in test_cases:
assert get_combined_digits(line) == expected, f"Failed on {line}"
# test sum_calibration_values function
assert sum_calibration_values([line for line, _ in test_cases]) == 142, "Sum test failed"
# indicate all tests passed
print("All tests passed")
if __name__ == "__main__":
# run the test function
test_calibration_values()
# execute the main function
main()
Part Two
--- Part Two ---
Your calculation isn't quite right. It looks like some of the digits are actually spelled out with letters: one, two, three, four, five, six, seven, eight, and nine also count as valid "digits".
Equipped with this new information, you now need to find the real first and last digit on each line. For example:
```
two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen
```
In this example, the calibration values are `29, 83, 13, 24, 42, 14, and 76`. Adding these together produces `281`.
What is the sum of all of the calibration values?
➡️ We face an evolved problem where words representing numbers (one to nine) are now also considered valid digits. We must identify the first and last “digits” on each line, with a digit being either a numerical character or a word representing a number.
Here, we need to modify our code to recognize these words or characters and convert them into their numerical equivalents, and then concatenate them to form a two-digit number for each line.
Let’s first update the testcases and then think about our logic:

💭 A simple “hacky” way of doing it is to just replace all the “word” digits with their “numerical” counterpart using a simple map and then our existing code should work!

💡Edge case: For lines like “eightwothree” where “T” of eight and two interesect, we need to give priority to “Eight” ~ 8 as the first digit. This took me a while to implement, but by keeping a record of index of each identified digit, it’s easy to sort them and update the line.
# --- Day 1: Trebuchet?! ---
# --- Part two ---
# mapping of spelled-out numbers to their numeric equivalents
WORD_TO_DIGIT_MAP = {
"one": "1",
"two": "2",
"three": "3",
"four": "4",
"five": "5",
"six": "6",
"seven": "7",
"eight": "8",
"nine": "9",
}
def get_combined_digits(line):
# positions stores tuples of (idx, digit) for each spelled-out
# number found in the line, this helps in replacing the spelled-out numbers
# with their corresponding digits, considering their order and overlaps in the string
positions = [
(idx, digit)
for word, digit in WORD_TO_DIGIT_MAP.items()
for idx in range(len(line))
if line.startswith(word, idx)
]
# add existing numerical digits and their positions
positions.extend((idx, ch) for idx, ch in enumerate(line) if ch.isdigit())
# handle no digits case
if not positions:
return 0
# sort by position idx
positions.sort(key=lambda x: x[0])
# extract first and last digit based on combined order
first, last = positions[0][1], positions[-1][1]
return int(first + last)
def sum_calibration_values(lines):
sum = 0
for line in lines:
tmp = get_combined_digits(line.strip())
# print(f"{line.strip()} -> {tmp}")
sum += tmp
return sum
def test_calibration_values():
test_cases = [
("two1nine", 29),
("eightwothree", 83),
("abcone2threexyz", 13),
("xtwone3four", 24),
("4nineeightseven2", 42),
("zoneight234", 14),
("7pqrstsixteen", 76),
("no digits here", 0)
]
for line, expected in test_cases:
assert get_combined_digits(line) == expected, f"Failed on {line}"
assert sum_calibration_values([line for line, _ in test_cases]) == 281, "Sum test failed"
print("All tests passed")
def main():
with open("day_1/input.txt") as f:
lines = f.readlines()
print(sum_calibration_values(lines))
if __name__ == "__main__":
# run the test function
test_calibration_values()
# execute the main function
main()
And voila! We got the correct answer! 🎉

p.s: I am deliberating not adding answers here so you can actually spend some time trying to understand how we solved the problem!
Feel free to checkout code on my Github repo and try to set it up and run on your machine 👇
advent-of-code-2023/day_1 at main · jatinkrmalik/advent-of-code-2023
Advent of Code is an Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can…github.com
Keep reading
🎄Solve Advent of Code 2024 With Me!
Announcing a spoiler-free YouTube video series for Advent of Code 2024 that focuses on how to analyze and approach each puzzle, not just the answers.
The Redis Paradox: my DelhiFOSS 2.0 talk at IIT Delhi, written up
The written version of my DelhiFOSS 2.0 talk on Redis beyond caching: a short history of caches, live driver matching with GEO commands, and Pub/Sub.
Interview prep, senior edition: a system design checklist, one backtracking template and what loops grade on
My 2022 prep notes for senior and staff interviews: a system design checklist, one Python DFS for seven backtracking problems, rubrics and a STAR story bank.
Leave a comment