Skip to main content

2016

Day 1: No Time for a Taxicab

Part 1

Navigate a grid following turn and movement instructions, find final Manhattan distance from start.

position = [0, 0, 0]  # x, y, direction

for instruction in instructions:
# Parse turn and distance
turn = instruction.strip()[:1]
distance = int(instruction.strip()[1:])

# Update direction (0=North, 1=East, 2=South, 3=West)
if turn == "R":
position[2] += 1
else:
position[2] -= 1
position[2] = position[2] % 4

# Move in current direction
if position[2] == 0:
position[1] += distance
elif position[2] == 1:
position[0] += distance
elif position[2] == 2:
position[1] -= distance
elif position[2] == 3:
position[0] -= distance

Part 2

Track all visited locations, find first location visited twice.

position = [0, 0, 0]
visited = []

for instruction in instructions:
turn = instruction.strip()[:1]
distance = int(instruction.strip()[1:])

# Update direction
position[2] = (position[2] + (1 if turn == "R" else -1)) % 4

# Move one step at a time and track locations
for _ in range(distance):
if position[2] == 0:
position[1] += 1
elif position[2] == 1:
position[0] += 1
elif position[2] == 2:
position[1] -= 1
elif position[2] == 3:
position[0] -= 1

location = (position[0], position[1])
if location in visited:
print(location)
visited.append(location)

Day 2: Bathroom Security

Part 1

Follow instructions to move on a 3x3 numpad, staying within bounds.

x, y = 0, 0  # Start at center (5)
code = []

for line in data:
for instruction in line.strip():
# Move according to instruction
if instruction == "U":
y += 1
elif instruction == "D":
y -= 1
elif instruction == "L":
x -= 1
elif instruction == "R":
x += 1

# Stay within bounds
x = max(-1, min(1, x))
y = max(-1, min(1, y))

# Convert position to keypad number
number = {
(-1, 1): 1, (0, 1): 2, (1, 1): 3,
(-1, 0): 4, (0, 0): 5, (1, 0): 6,
(-1,-1): 7, (0,-1): 8, (1,-1): 9
}[(x, y)]
code.append(number)

Part 2

Similar movement but on a diamond-shaped keypad with different bounds.

x, y = -2, 0  # Start at 5
code = []

for line in data:
for instruction in line.strip():
old_x, old_y = x, y

if instruction == "U":
y += 1
elif instruction == "D":
y -= 1
elif instruction == "L":
x -= 1
elif instruction == "R":
x += 1

# Stay within diamond shape using Manhattan distance
if abs(x) + abs(y) > 2:
x, y = old_x, old_y

# Convert position to keypad character using lookup
number = {
(0, 2): '1',
(-1,1): '2', (0, 1): '3', (1, 1): '4',
(-2,0): '5', (-1,0): '6', (0, 0): '7', (1, 0): '8', (2, 0): '9',
(-1,-1):'A', (0,-1): 'B', (1,-1): 'C',
(0,-2): 'D'
}[(x, y)]
code.append(number)

Day 3: Squares With Three Sides

Part 1

Check if each row of numbers could form a valid triangle (sum of any two sides must be greater than third side).

triangles = []
success = []

for line in data:
triangle = [int(num) for num in line.split()]
if max(triangle) < (sum(triangle)/2):
success.append(1)

print(sum(success))

Part 2

Same validation but read numbers in columns of three instead of rows.

# Convert input to matrix
matrix = []
for line in data:
triangle = [int(num) for num in line.split()]
matrix.append(triangle)

# Read by columns
triangles = []
for i in range(0, len(matrix), 3):
for col in range(3):
triangle = [matrix[i+row][col] for row in range(3)]
triangles.append(triangle)

# Check validity
success = sum(1 for triangle in triangles
if max(triangle) < sum(triangle)/2)

Day 4: Security Through Obscurity

Part 1

Validate room names and sum sector IDs of real rooms using checksum verification.

def part1(input):
sectors = 0
valid_rooms = []

for room in input:
# Parse room data
name = sorted(room[0][:-3])
sector = room[0][-3:]
checksum = room[1]

# Count letter frequencies
char_counts = {}
for char in name:
char_counts[char] = name.count(char)

# Sort by count (descending) then alphabetically
sorted_chars = sorted(char_counts.items(),
key=lambda x: (100-x[1], x[0]))[:5]

# Verify checksum
calculated_check = ''.join(char for char, _ in sorted_chars)
if sorted(calculated_check) == sorted(checksum):
sectors += int(sector)
valid_rooms.append((room[0][:-3], sector))

return valid_rooms

Part 2

Decrypt room names using Caesar cipher to find specific room.

def caesar(text, shift):
intab = string.ascii_lowercase
outtab = intab[shift:] + intab[:shift]
trantab = str.maketrans(intab, outtab)
return text.translate(trantab)

def part2(valid_rooms):
for name, sector in valid_rooms:
shift = int(sector) % 26
if caesar(name, shift) == 'northpoleobjectstorage':
return sector

Day 5: How About a Nice Game of Chess?

Part 1

Generate password by finding MD5 hashes starting with five zeros.

def part1(door_id):
index = 0
password = list('########')
position = 0

while '#' in password:
candidate = hashlib.md5((door_id + str(index)).encode()).hexdigest()
if candidate.startswith('00000'):
password[position] = candidate[5]
position += 1
index += 1

return ''.join(password)

Part 2

Similar, but use sixth character as position and seventh as password character.

def part2(door_id):
index = 0
password = list('########')

while '#' in password:
candidate = hashlib.md5((door_id + str(index)).encode()).hexdigest()
if candidate.startswith('00000'):
position = candidate[5]
if position in '01234567' and password[int(position)] == '#':
password[int(position)] = candidate[6]
index += 1

return ''.join(password)