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