Skip to main content

2020

Day 1: Report Repair

Part 1: Find Pair

Find two entries that sum to 2020 and multiply them. Using itertools for efficient pair combinations.

import itertools

expenses = [int(line.strip()) for line in data]

for pair in itertools.combinations(expenses, 2):
if pair[0] + pair[1] == 2020:
print(pair[0] * pair[1])

Part 2: Find Triplet

Same task but with three numbers instead of two.

for pair in itertools.combinations(expenses, 3):
if pair[0] + pair[1] + pair[2] == 2020:
print(pair[0] * pair[1] * pair[2])

Useful Tools

  • itertools.combinations for unique pairs/triplets
  • List comprehension for data loading
  • Simple arithmetic checks

Day 2: Password Philosophy

Part 1: Count Valid Passwords

Check if number of occurrences of a character in password falls within range.

for line in data:
password = line.strip().split(":")[1]
character = line.strip().split(":")[0].split()[1]
upper = int(line.strip().split(":")[0].split()[0].split("-")[1])
lower = int(line.strip().split(":")[0].split()[0].split("-")[0])

if lower <= password.count(character) <= upper:
result += 1

Part 2: Position Validation

Character must appear at exactly one of two positions (XOR).

if (password[lower] == character) ^ (password[upper] == character):
result += 1

Useful Tools

  • String splitting for parsing input
  • XOR operator for exact matches
  • String count method

Day 3: Toboggan Trajectory

Part 1: Count Trees

Navigate slope moving right 3, down 1, checking for trees (#). Pattern repeats horizontally.

for i in range(0, len(slope)-1):
down += 1
across += 3
if slope[down][across % len(slope[down])] == '#':
count += 1

Part 2: Multiple Slopes

Check multiple slopes and multiply results.

def checkslope(downnum, acrossnum, slope):
down, across = 0, 0
count = 0
for i in range(0, int((len(slope)-1)/downnum)):
down += downnum
across += acrossnum
if slope[down][across % len(slope[down])] == '#':
count += 1
return count

result = checkslope(1,1,slope) * checkslope(1,3,slope) * \
checkslope(1,5,slope) * checkslope(1,7,slope) * \
checkslope(2,1,slope)

Useful Tools

  • Modulo for wrapping pattern
  • Functions for reusable slope checking
  • Integer division for step counting

Day 4: Passport Processing

Part 1: Required Fields

Check passports for presence of all required fields (excluding cid).

mandatory = ["hcl","iyr","eyr","ecl","pid","byr","hgt"]

# Parse input into dictionaries
entry = {}
for line in data:
if line.strip() != "":
for pair in line.strip().split():
key, value = pair.split(":")
entry[key] = value
else:
parsed.append(entry)
entry = {}

# Count entries with all mandatory fields
for entry in parsed:
fieldcount = 0
for field in mandatory:
if field in entry:
fieldcount += 1
if fieldcount == 7:
count += 1

Part 2: Field Validation

Add specific validation rules for each field using regex.

def validate(key, value):
if key == "byr":
if re.search("^[0-9]{4}$", value):
if 1920 <= int(value) <= 2002:
return 1
elif key == "hgt":
if match := re.search("^([0-9]{2,3})((in)|(cm))$", value):
if match[2] == 'cm':
if 150 <= int(match[1]) <= 193:
return 1
elif match[2] == 'in':
if 59 <= int(match[1]) <= 76:
return 1
elif key == "hcl":
if re.search("^#([0-9]|[a-f]){6}$", value):
return 1
elif key == "ecl":
if value in {'amb','blu','brn','gry','grn','hzl','oth'}:
return 1
return 0

Useful Tools

  • Regular expressions for validation
  • Dictionary for field storage
  • Set for valid value checking
  • String splitting for parsing

Day 5: Binary Boarding

Part 1: Decode Seat IDs

Convert boarding pass codes to seat IDs using binary conversion. F/L = 0, B/R = 1.

def read_boarding(input):
rowdata = input[0:7].replace('F','0').replace('B','1')
coldata = input[7:].replace('L','0').replace('R','1')

rowdec = int(rowdata, 2)
coldec = int(coldata, 2)

return rowdec * 8 + coldec

# Find highest seat ID
passes = [read_boarding(line) for line in data]
print(max(passes))

Part 2: Find Missing Seat

Find the missing seat ID that has occupied seats on either side.

for seat in range(0, (128*8)):
if (seat not in passes and
seat-1 in passes and
seat+1 in passes):
print(seat)

Useful Tools

  • String replace for binary conversion
  • int(x, 2) for binary to decimal
  • Range to check all possible seats
  • List membership testing

Day 6: Custom Customs

Part 1: Any Yes

Count unique questions answered yes in each group.

result = 0
for group in data.split('\n\n'):
# Remove newlines and count unique characters
result += len(set(group.replace("\n", "")))

Part 2: All Yes

Count questions everyone in group answered yes to.

result = 0
for group in data.split('\n\n'):
questions = 'abcdefghijklmnopqrstuvwxyz'
for item in group.split('\n'):
# Find common letters between current and previous answers
questions = ''.join(set(questions).intersection(set(item)))
result += len(questions)

Useful Tools

  • Set operations (intersection)
  • String splitting on double newlines
  • join() to recombine sets
  • String replace for cleanup

Day 7: Handy Haversacks

Part 1: Containing Bags

Parse bag rules and count how many bags can eventually contain a shiny gold bag.

# Parse rules with regex
for line in lines:
contentlist = {}
bag = line.split(' bags contain')
for contents in bag[1].split(","):
r = re.findall('(\d|no) (.*) bag', contents.strip('.').strip())
if r[0][0] != 'no':
contentlist[r[0][1]] = r[0][0]
input.append((bag[0], contentlist))

def part1(input):
bags = nx.DiGraph()
for bag in input:
for contents in bag[1]:
bags.add_edge(bag[0], contents)
return len(nx.ancestors(bags, 'shiny gold'))

Part 2: Required Bags

Count total bags required inside a shiny gold bag using weighted edges.

def part2(input):
bags = nx.DiGraph()
for bag in input:
for contents in bag[1]:
bags.add_edge(bag[0], contents, weight=int(bag[1][contents]))

def dfs_cost(node):
cost = 0
for n, weight in bags[node].items():
cost += weight['weight'] * (1 + dfs_cost(n))
return cost

return dfs_cost('shiny gold')

Useful Tools

  • NetworkX for graph representation
  • Regular expressions for parsing rules
  • DFS for recursive counting
  • Weighted edges for bag quantities

Day 8: Handheld Halting

Part 1: Find Loop

Execute instructions until one repeats, return accumulator value. Similar to 2019 Day 2: 1202 Program Alarm but simpler.

def execute_command(command, index, result):
if command[0] == 'nop':
index += 1
elif command[0] == 'acc':
index += 1
result += int(command[1])
elif command[0] == 'jmp':
index += int(command[1])
return index, result

def part1(input):
visited = []
index = 0
result = 0
while index not in visited:
visited.append(index)
index, result = execute_command(input[index], index, result)
return result

Part 2: Fix Program

Try flipping each nop/jmp instruction until program completes.

def flip_instruction(this_input, flip):
output = deepcopy(this_input)
if output[flip][0] == 'nop':
output[flip][0] = 'jmp'
elif output[flip][0] == 'jmp':
output[flip][0] = 'nop'
return output

def part2(input):
for flip in range(0, len(input)):
flipped_input = flip_instruction(input, flip)
visited = []
index = 0
result = 0
while index not in visited:
visited.append(index)
if index >= len(input):
return result
index, result = execute_command(flipped_input[index],
index, result)

Useful Tools

  • deepcopy for program modification
  • Lists for tracking visited instructions
  • Simple state machine implementation
  • Index/accumulator tracking