Dazbo's Advent of Code solutions, written in Python
Day 4: Security Through Obscurity
Regular Expressions (Regex)DataclassesCollections.Counterstring moduleord() and chr()Modulo Operator
In this Advent of Code puzzle, we encounter a list of rooms, each with an encrypted name, a sector ID, and a checksum. Our task is to identify “real” rooms by validating their checksums and then perform a decryption process on the names of these real rooms.
A room is considered “real” if its checksum matches the five most common letters in its encrypted name. In case of ties in frequency, letters are sorted alphabetically.
Here’s an example of the input data format:
aaaaa-bbb-z-y-x-123[abxyz]
a-b-c-d-e-f-g-h-987[abcde]
not-a-real-room-404[oarel]
totally-real-room-200[decoy]
The first part of the puzzle requires us to calculate the sum of the sector IDs of all the “real” rooms.
collections.Counter
to efficiently find the five most common characters.Counter
naturally handles this by sorting alphabetically, which aligns with the puzzle’s rules.import re
from dataclasses import dataclass
from collections import Counter
import string
@dataclass
class Room:
enc_name: str
sector_id: int
checksum: str
def is_valid_room(room: Room) -> bool:
char_counts = {char:room.enc_name.count(char) for char in string.ascii_lowercase}
top_five_counts = Counter(char_counts).most_common(5)
top_five_chars = "".join([item[0] for item in top_five_counts])
return top_five_chars == room.checksum
def main():
# ... (file reading and setup) ...
pattern = re.compile(r"(\D+)-(\d+)\[([a-z]{5})\]")
rooms: list[Room] = []
for line in data:
match = pattern.match(line)
if match:
enc_name, sector_id, checksum = match.groups()
room = Room(enc_name, int(sector_id), checksum)
if is_valid_room(room):
rooms.append(room)
sector_id_sum = sum(room.sector_id for room in rooms)
# ... (logging) ...
In the is_valid_room
function:
char_counts
to store the frequency of each lowercase letter in the enc_name
.collections.Counter
is then used to get the most_common(5)
characters. This handles both frequency and alphabetical tie-breaking.top_five_chars
which is then compared with the room.checksum
.For the second part, we need to decrypt the names of the real rooms to find a specific room: “northpole object storage”.
The decryption process involves two steps:
-
) in the encrypted name are replaced with spaces.import string
def decrypt_room(room: Room) -> str:
enc_str = room.enc_name.replace("-", " ")
decrypted_str = []
for char in enc_str:
if char in string.ascii_lowercase:
ascii_code = ord(char)
ascii_code += (room.sector_id % 26) # Shift by sector_id, modulo 26 for wrap-around
if (ascii_code > ord('z')): # If we exceed 'z', wrap back to 'a'
ascii_code -= 26
decrypted_str.append(chr(ascii_code))
else:
decrypted_str.append(char) # Keep spaces as they are
return "".join(decrypted_str)
def main():
# ... (Part 1 code) ...
for room in rooms:
decrypted_room = decrypt_room(room)
if decrypted_room == "northpole object storage":
# ... (logging) ...
break
In the decrypt_room
function:
ord()
.sector_id
(modulo 26 to handle shifts larger than the alphabet size) to the ASCII value.chr()
and append it to our decrypted_str
.This puzzle effectively combines string manipulation, regular expressions, and character encoding/decoding. The use of collections.Counter
simplifies the checksum validation, while basic ASCII arithmetic handles the character shifting for decryption. The solution is efficient and directly addresses both parts of the problem.