├── 2015 ├── day_01.py ├── day_17.py ├── day_05.py ├── day_04.py ├── day_12.py ├── day_24.py ├── day_10.py ├── day_20.py ├── day_23.py ├── day_11.py ├── day_25.py ├── day_02.py ├── day_08.py ├── day_19.py ├── day_09.py ├── day_16.py ├── day_13.py ├── day_14.py ├── day_03.py ├── day_18.py └── day_15.py ├── 2016 ├── day_15.py ├── day_06.py ├── day_20.py ├── day_12.py ├── day_19.py ├── day_16.py ├── day_05.py ├── day_03.py ├── day_01.py ├── day_18.py ├── day_22.py ├── day_08.py ├── day_02.py ├── day_07.py ├── day_04.py ├── day_14.py ├── day_13.py ├── day_17.py ├── day_21.py └── day_23.py ├── 2017 ├── day_01.py ├── day_13.py ├── day_09.py ├── day_04.py ├── day_12.py ├── day_11.py ├── day_19.py ├── day_10.py ├── day_17.py ├── day_05.py ├── day_24.py ├── day_02.py ├── day_03.py ├── day_06.py ├── day_07.py ├── day_15.py ├── day_25.py └── day_16.py ├── 2018 ├── day_01.py ├── day_09.py ├── day_14.py ├── day_05.py ├── day_02.py ├── day_08.py ├── day_11.py ├── day_20.py ├── day_03.py ├── day_12.py ├── day_25.py ├── day_18.py ├── day_13.py ├── day_07.py └── day_06.py ├── 2019 ├── day_01.py ├── day_04.py ├── day_06.py ├── day_02.py ├── day_16.py ├── day_08.py ├── day_14.py ├── day_10.py ├── day_22.py ├── day_03.py └── day_05.py ├── 2020 ├── day_25.py ├── day_15.py ├── day_06.py ├── day_13.py ├── day_02.py ├── day_09.py ├── day_21.py ├── day_03.py ├── day_10.py ├── day_05.py ├── day_19.py ├── day_14.py ├── day_12.py ├── day_08.py ├── day_24.py └── day_22.py ├── 2021 ├── day_01.py ├── day_07.py ├── day_02.py ├── day_06.py ├── day_13.py ├── day_25.py ├── day_17.py ├── day_11.py ├── day_09.py ├── day_10.py ├── day_20.py ├── day_14.py ├── day_03.py ├── day_05.py ├── day_04.py ├── day_15.py └── day_12.py ├── 2022 ├── go.mod ├── day_06 │ └── main.go ├── day_08 │ └── main.go ├── day_01 │ └── main.go ├── day_25 │ └── main.go ├── day_10 │ └── main.go ├── day_02 │ └── main.go ├── day_12 │ └── main.go ├── day_04 │ └── main.go ├── day_09 │ └── main.go └── day_03 │ └── main.go ├── .gitignore ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | */inputs/ 2 | getinput 3 | **input.txt 4 | -------------------------------------------------------------------------------- /2022/go.mod: -------------------------------------------------------------------------------- 1 | module aoc-2022 2 | 3 | go 1.21.2 4 | -------------------------------------------------------------------------------- /2020/day_25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 25 - Combo Breaker.""" 4 | 5 | 6 | with open('inputs/day_25.txt') as f: 7 | card_key, door_key = [int(line.strip()) for line in f.readlines()] 8 | 9 | num = 1 10 | loops = 0 11 | while True: 12 | loops += 1 13 | num *= 7 14 | num = num % 20201227 15 | if num == card_key: 16 | break 17 | 18 | num = 1 19 | for loop in range(loops): 20 | num = num * door_key 21 | num = num % 20201227 22 | 23 | # Answer One 24 | print("Encryption Key:", num) 25 | -------------------------------------------------------------------------------- /2021/day_01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 1 - Sonar Sweep""" 4 | 5 | 6 | with open('inputs/day_01.txt', 'r') as readings: 7 | depths = [int(number.strip()) for number in readings.readlines()] 8 | 9 | increased = sum([1 for i in range(1, len(depths)) if depths[i] > depths[i-1]]) 10 | 11 | # Answer One 12 | print("Number of times the depth increases:", increased) 13 | 14 | increased = sum([1 for i in range(3, len(depths)) if depths[i] > depths[i-3]]) 15 | 16 | # Answer Two 17 | print("Number of times the sliding window increases:", increased) 18 | -------------------------------------------------------------------------------- /2018/day_01.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 1 - Chronal Calibration""" 2 | 3 | 4 | with open('inputs/day_01.txt') as f: 5 | shifts = [int(x) for x in f.readlines()] 6 | 7 | # Answer One 8 | print("The final frequency is:", sum(shifts)) 9 | 10 | frequencies = set([0]) 11 | frequency = 0 12 | no_repeats = True 13 | while no_repeats: 14 | for shift in shifts: 15 | frequency += shift 16 | if frequency in frequencies: 17 | no_repeats = False 18 | break 19 | frequencies.add(frequency) 20 | 21 | # Answer Two 22 | print("The first repeated frequency is:", frequency) 23 | -------------------------------------------------------------------------------- /2015/day_01.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 1 - Not Quite Lisp""" 2 | 3 | def direction(part_two=False): 4 | with open('inputs/day_01.txt') as f: 5 | instructions = f.read() 6 | level = 0 7 | position = 0 8 | for char in instructions: 9 | if char == '(': 10 | level += 1 11 | elif char ==')': 12 | level -= 1 13 | position += 1 14 | 15 | if part_two: 16 | if level <= -1: 17 | print("First time in Basement =", position) 18 | return 19 | print("Final Floor =", level) 20 | 21 | # Answer to Part One 22 | direction() 23 | 24 | # Answer to Part Two 25 | direction(part_two=True) 26 | -------------------------------------------------------------------------------- /2015/day_17.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 17 - No Such Thing as Too Much""" 2 | 3 | import itertools 4 | 5 | with open('inputs/day_17.txt') as f: 6 | containers = [int(container) for container in f] 7 | 8 | num_needed = len(containers) 9 | total = 0 10 | min_combos = 0 11 | for i in range(len(containers)): 12 | for combination in itertools.combinations(containers, i): 13 | if sum(combination) == 150: 14 | total += 1 15 | 16 | if i <= num_needed: 17 | num_needed = i 18 | min_combos += 1 19 | 20 | # Answer Part One 21 | print("Number of Valid Combinations =", total) 22 | 23 | # Answer Part Two 24 | print("Number of Valid Minimum Container Combinations =", min_combos) 25 | -------------------------------------------------------------------------------- /2016/day_15.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 15 - Timing is Everything""" 2 | 3 | with open('inputs/day_15.txt') as f: 4 | disc_info = [line.strip() for line in f.readlines()] 5 | 6 | discs = [] 7 | for info in disc_info: 8 | parse = info.split(' ') 9 | discs.append([int(parse[3]), int(parse[-1][:-1])]) 10 | 11 | discs.append([11, 0]) # Delete this for part one 12 | 13 | time = 0 14 | while True: 15 | passed = True 16 | for delay, disc in enumerate(discs, 1): 17 | if (disc[1] + time + delay) % disc[0] != 0: 18 | passed = False 19 | break 20 | 21 | if passed: 22 | break 23 | 24 | time += 1 25 | 26 | # Answer One / Answer Two 27 | print("Time to wait before pressing button:", time) 28 | -------------------------------------------------------------------------------- /2016/day_06.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 6 - Signals and Noise""" 2 | 3 | 4 | with open('inputs/day_06.txt', 'r') as f: 5 | rows = [row.strip() for row in f.readlines()] 6 | 7 | flipped = zip(*rows) 8 | 9 | message = '' 10 | mod_message = '' 11 | for chars in flipped: 12 | most_freq = '' 13 | least_freq = '' 14 | highest = 0 15 | lowest = 100 16 | for char in chars: 17 | if chars.count(char) > highest: 18 | highest = chars.count(char) 19 | most_freq = char 20 | if chars.count(char) < lowest: # Part Two 21 | lowest = chars.count(char) 22 | least_freq = char 23 | message += most_freq 24 | mod_message += least_freq 25 | 26 | # Answer One 27 | print("Error Corrected Message:", message) 28 | 29 | # Answer Two 30 | print("Modified Message:", mod_message) 31 | -------------------------------------------------------------------------------- /2015/day_05.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 5 - Doesn't He Have Intern-Elves For This?""" 2 | 3 | import re 4 | 5 | with open('inputs/day_05.txt') as f: 6 | strings = f.readlines() 7 | 8 | # Answer One Regexes 9 | naughty_regex = re.compile(r'ab|cd|pq|xy') 10 | vowel_regex = re.compile(r'([aeiou].*){3,}') 11 | double_regex = re.compile(r'(.)\1') 12 | # Answer Two Regexes 13 | repeated_regex = re.compile(r'(..).*\1') 14 | gapped_regex = re.compile(r'(.).\1') 15 | 16 | nice = 0 17 | nice_2 = 0 18 | for string in strings: 19 | 20 | if (vowel_regex.search(string) and double_regex.search(string) 21 | and not naughty_regex.search(string)): 22 | nice += 1 23 | 24 | if repeated_regex.search(string) and gapped_regex.search(string): 25 | nice_2 += 1 26 | 27 | 28 | print("Answer One =", nice) 29 | print("Answer Two =", nice_2) 30 | -------------------------------------------------------------------------------- /2015/day_04.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 4 - The Ideal Stocking Stuffer""" 2 | 3 | import hashlib 4 | 5 | def mine_adventcoins(part_two=False): 6 | """Calculates and returns MD5 Hash with five leading zeroes.""" 7 | number = 0 8 | while True: 9 | hexed = hashlib.md5('{}{}'.format(key, number).encode('utf-8')).hexdigest() 10 | 11 | if part_two: 12 | if str(hexed)[0:6] == '000000': 13 | return number 14 | 15 | else: 16 | if str(hexed)[0:5] == '00000': 17 | return number 18 | 19 | number += 1 20 | 21 | 22 | with open('inputs/day_04.txt', 'r') as f: 23 | key = f.read().strip() 24 | 25 | # Answer Part One 26 | print("Five leading zeroes with =", mine_adventcoins()) 27 | 28 | # Answer Part Two 29 | print("Six leading zeroes with =", mine_adventcoins(part_two=True)) 30 | -------------------------------------------------------------------------------- /2015/day_12.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 12 - JSAbacusFramework.io""" 2 | 3 | import re 4 | import json 5 | 6 | with open('inputs/day_12.txt') as f: 7 | document = f.read() 8 | 9 | # Part One 10 | numbers = re.findall(r'-?[0-9]+', document) 11 | 12 | total = 0 13 | for number in numbers: 14 | total += int(number) 15 | 16 | # Answer One 17 | print("Sum =", total) 18 | 19 | 20 | # Part Two 21 | def no_red(obj): 22 | """Evaluate json objects adding numbers not in dicts containing "red".""" 23 | if type(obj) == int: 24 | return obj 25 | if type(obj) == list: 26 | return sum([no_red(item) for item in obj]) 27 | if type(obj) == dict: 28 | if 'red' in obj.values(): 29 | return 0 30 | return no_red(list(obj.values())) 31 | return 0 32 | 33 | 34 | # Answer Two 35 | print("Corrected Sum =", no_red(json.loads(document))) 36 | -------------------------------------------------------------------------------- /2018/day_09.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 9 - Marble Mania""" 2 | 3 | import collections 4 | 5 | 6 | def marble_game(num_players, num_marbles): 7 | """Play a game of marbles and return the winning score.""" 8 | circle = collections.deque([0]) 9 | scores = collections.defaultdict(int) 10 | player = 1 11 | 12 | for marble in range(1, num_marbles + 1): 13 | if marble % 23 == 0: 14 | player = player % num_players 15 | circle.rotate(7) 16 | scores[player] += circle.pop() + marble 17 | circle.rotate(-1) 18 | else: 19 | circle.rotate(-1) 20 | circle.append(marble) 21 | 22 | player += 1 23 | 24 | return max(scores.values()) 25 | 26 | 27 | # Answer One 28 | print("Small game:", marble_game(465, 71940)) 29 | 30 | # Answer Two 31 | print("Large game:", marble_game(465, 7194000)) -------------------------------------------------------------------------------- /2020/day_15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 15 - Rambunctious Recitation.""" 4 | 5 | 6 | starting_numbers = [11, 0, 1, 10, 5, 19] 7 | spoken_dict = {} 8 | spoken_set = set() 9 | turn = 1 10 | 11 | for number in starting_numbers[:-1]: 12 | spoken_dict[number] = turn 13 | spoken_set.add(number) 14 | turn += 1 15 | last_spoken = starting_numbers[-1] 16 | 17 | while turn < 30000000: 18 | 19 | if last_spoken not in spoken_set: 20 | number = 0 21 | else: 22 | number = turn - spoken_dict[last_spoken] 23 | 24 | spoken_dict[last_spoken] = turn 25 | spoken_set.add(last_spoken) 26 | 27 | if turn == 2020: 28 | # Answer One 29 | print("2020th Number spoken:", last_spoken) 30 | 31 | last_spoken = number 32 | turn += 1 33 | 34 | # Answer Two 35 | print("30000000th Number spoken:", last_spoken) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2 | 3 | ## My solutions to the [Advent of Code](https://adventofcode.com/) challenges by [Eric Wastl](https://github.com/topaz) 4 | 5 | ### Overall Stars Collected (350/350): 6 | 7 | * #### [2015](https://adventofcode.com/2015) - Stars Collected (50/50) 8 | 9 | * #### [2016](https://adventofcode.com/2016) - Stars Collected (50/50) 10 | 11 | * #### [2017](https://adventofcode.com/2017) - Stars Collected (50/50) 12 | 13 | * #### [2018](https://adventofcode.com/2018) - Stars Collected (50/50) 14 | 15 | * #### [2019](https://adventofcode.com/2019) - Stars Collected (50/50) 16 | 17 | * #### [2020](https://adventofcode.com/2020) - Stars Collected (50/50) > > > [Blog Posts](https://blog.findlayian.com/tags/aoc-2020) 18 | 19 | * #### [2021](https://adventofcode.com/2021) - Stars Collected (50/50) > > > [Blog Posts](https://blog.findlayian.com/tags/aoc-2021) 20 | -------------------------------------------------------------------------------- /2019/day_01.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 01 - The Tyranny of the Rocket Equation.""" 2 | 3 | 4 | def calculate_recursive_fuel(fuel_mass): 5 | """Calculate the recursive fuel needed to launch a mass of fuel.""" 6 | new_fuel_mass = fuel_mass // 3 - 2 7 | if new_fuel_mass <= 0: 8 | return 0 9 | return new_fuel_mass + calculate_recursive_fuel(new_fuel_mass) 10 | 11 | 12 | with open ('inputs/day_01.txt', 'r') as f: 13 | masses = [int(module_mass) for module_mass in f.readlines()] 14 | 15 | naive_fuel_sum = 0 16 | true_fuel_sum = 0 17 | for module_mass in masses: 18 | fuel = module_mass // 3 - 2 19 | naive_fuel_sum += fuel 20 | true_fuel_sum += fuel + calculate_recursive_fuel(fuel) 21 | 22 | # Answer One 23 | print("The naive fuel sum is {}".format(naive_fuel_sum)) 24 | 25 | # Answer Two 26 | print("The true fuel sum is {}".format(true_fuel_sum)) 27 | 28 | -------------------------------------------------------------------------------- /2017/day_01.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 1 - Inverse Captcha""" 2 | 3 | 4 | def captcha_dupes(numbers): 5 | """Sum only the digits that match the one next in a cyclic string.""" 6 | Total = 0 7 | for i in range(len(numbers)): 8 | if numbers[i] == numbers[i - 1]: 9 | Total += int(numbers[i]) 10 | return Total 11 | 12 | 13 | def captcha_halfway(numbers): 14 | """Sum the digits that match the one half way around a cyclic string.""" 15 | total = 0 16 | for i in range(int(len(numbers) / 2)): 17 | if numbers[i] == numbers[i + int(len(numbers) / 2)]: 18 | total += int(numbers[i]) 19 | 20 | return total * 2 21 | 22 | 23 | with open('inputs/day_01.txt') as f: 24 | captcha = f.read().strip() 25 | 26 | # Answer One 27 | print("Captcha:", captcha_dupes(captcha)) 28 | 29 | # Answer Two 30 | print("Second Captcha:", captcha_halfway(captcha)) 31 | -------------------------------------------------------------------------------- /2016/day_20.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 20 - Firewall Rules""" 2 | 3 | with open('inputs/day_20.txt') as f: 4 | ip_ranges = [line.strip() for line in f.readlines()] 5 | 6 | blacklisted = [] 7 | for ip_range in ip_ranges: 8 | parse = ip_range.split('-') 9 | start, end = int(parse[0]), int(parse[1]) 10 | blacklisted.append((start, end)) 11 | 12 | lowest = None 13 | allowed = 0 14 | target = 0 15 | while target <= 4294967295: 16 | unblocked = True 17 | for listed in blacklisted: 18 | if target >= listed[0] and target <= listed[1]: 19 | target = listed[1] 20 | unblocked = False 21 | break 22 | 23 | if unblocked: 24 | allowed += 1 25 | if not lowest: 26 | lowest = target 27 | 28 | target += 1 29 | 30 | # Answer One 31 | print("Lowest unblocked IP:", lowest) 32 | 33 | # Answer Two 34 | print("Unblocked IPs:", allowed) 35 | -------------------------------------------------------------------------------- /2021/day_07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 7 - The Treachery of Whales""" 4 | 5 | 6 | with open('inputs/day_07.txt', 'r') as aoc_input: 7 | crabs = [int(x) for x in aoc_input.read().strip().split(',')] 8 | 9 | least_fuel = None 10 | for position in range(max(crabs)): 11 | fuel_cost = sum([abs(x - position) for x in crabs]) 12 | if not least_fuel or fuel_cost < least_fuel: 13 | least_fuel = fuel_cost 14 | 15 | # Part One 16 | print("Fuel cost to align to least expensive position:", least_fuel) 17 | 18 | least_fuel = None 19 | for position in range(max(crabs)): 20 | 21 | fuel_cost = 0 22 | for crab in crabs: 23 | movement = abs(crab - position) 24 | fuel_cost += movement * (movement + 1) // 2 25 | 26 | if not least_fuel or fuel_cost < least_fuel: 27 | least_fuel = fuel_cost 28 | 29 | # Part Two 30 | print("Actual fuel cost to align to least expensive position:", least_fuel) 31 | -------------------------------------------------------------------------------- /2020/day_06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 06 - Custom Customs.""" 4 | 5 | 6 | with open ('inputs/day_06.txt', 'r') as forms: 7 | groups = [group_answers.strip() for group_answers in forms.read().split('\n\n')] 8 | 9 | overall_yes = 0 10 | for group in groups: 11 | group_yes = set() 12 | for member_yes in group.split('\n'): 13 | for char in member_yes: 14 | group_yes.add(char) 15 | 16 | overall_yes += len(group_yes) 17 | 18 | # Answer One 19 | print("Sum of all groups yes counts:", overall_yes) 20 | 21 | overall_group_yes = 0 22 | for group in groups: 23 | members = group.split('\n') 24 | group_yes = set([char for char in members[0]]) 25 | for member in members[1:]: 26 | group_yes = set([char for char in member]).intersection(group_yes) 27 | 28 | overall_group_yes += len(group_yes) 29 | 30 | # Answer Two 31 | print("Sum of all group overall yes counts:", overall_group_yes) 32 | -------------------------------------------------------------------------------- /2015/day_24.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 24 - It Hangs in the Balance""" 2 | 3 | import itertools 4 | 5 | 6 | with open('inputs/day_24.txt') as f: 7 | weights = [int(num) for num in f] 8 | 9 | section_weight = sum(weights) // 4 # Change to 3 for Part One 10 | 11 | smallest_section = len(weights) 12 | smallest_combos = [] 13 | 14 | for i in range(1, len(weights)): 15 | if i > smallest_section: 16 | break 17 | 18 | combos = itertools.combinations(weights, i) 19 | for combo in combos: 20 | if sum(combo) != section_weight: 21 | continue 22 | 23 | smallest_combos.append(combo) 24 | smallest_section = i 25 | 26 | smallest_qe = max(weights) ** len(weights) 27 | for combination in smallest_combos[:1]: 28 | qe = 1 29 | for weight in combination: 30 | qe *= weight 31 | 32 | if qe < smallest_qe: 33 | smallest_qe = qe 34 | 35 | # Answer 36 | print("Smallest Quantum Entanglement =", smallest_qe) 37 | 38 | -------------------------------------------------------------------------------- /2020/day_13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 13 - Shuttle Search.""" 4 | 5 | 6 | with open ('inputs/day_13.txt', 'r') as f: 7 | rows = [row.strip() for row in f.readlines()] 8 | 9 | early_time = int(rows[0]) 10 | bus_ids = rows[1].split(',') 11 | 12 | shortest_wait = None 13 | for bus_id in [int(x) for x in bus_ids if x != 'x']: 14 | difference = ((early_time // bus_id + 1) * bus_id) - early_time 15 | if shortest_wait is None or difference < shortest_wait[0]: 16 | shortest_wait = (difference, bus_id) 17 | 18 | # Answer One 19 | print("Product of bus ID and wait:", shortest_wait[0] * shortest_wait[1]) 20 | 21 | time = 1 22 | interval = 1 23 | for index, bus in enumerate([int(x) if x != 'x' else 1 for x in bus_ids]): 24 | while True: 25 | if (time + index) % bus == 0: 26 | interval *= bus 27 | break 28 | time += interval 29 | 30 | # Answer Two 31 | print("Earliest time where bus departure offsets match their indicies:", time) 32 | -------------------------------------------------------------------------------- /2016/day_12.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 12 - Leonardo's Monorail""" 2 | 3 | from collections import defaultdict 4 | 5 | 6 | with open('inputs/day_12.txt') as f: 7 | instructions = [line.strip() for line in f.readlines()] 8 | 9 | registers = defaultdict(int) 10 | registers['c'] = 1 # Comment Out for Part One 11 | i = 0 12 | while i < len(instructions): 13 | parse = instructions[i].split(' ') 14 | 15 | if parse[0] == 'cpy': 16 | if parse[1].isnumeric(): 17 | registers[parse[2]] = int(parse[1]) 18 | else: 19 | registers[parse[2]] = registers[parse[1]] 20 | 21 | elif parse[0] == 'inc': 22 | registers[parse[1]] += 1 23 | 24 | elif parse[0] == 'dec': 25 | registers[parse[1]] -= 1 26 | 27 | elif parse[0] == 'jnz': 28 | if parse[1].isnumeric(): 29 | if parse[1] != 0: 30 | i += int(parse[2]) - 1 31 | 32 | elif registers[parse[1]] != 0: 33 | i += int(parse[2]) - 1 34 | 35 | i += 1 36 | 37 | # Answer One / Answer Two 38 | print("Register A:", registers['a']) 39 | -------------------------------------------------------------------------------- /2015/day_10.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 10 - Elves Look, Elves Say""" 2 | 3 | 4 | def say(number): 5 | """Returns a numbers 'say' equivalent.""" 6 | numbers = [] 7 | spans = [] 8 | 9 | i = 0 10 | repeats = 1 11 | while i < len(number) - 1: 12 | if number[i] == number[i + 1]: 13 | repeats += 1 14 | else: 15 | numbers.append(number[i]) 16 | spans.append(str(repeats)) 17 | repeats = 1 18 | i += 1 19 | 20 | if number[-1] != number[-2]: 21 | numbers.append(number[-1]) 22 | spans.append('1') 23 | 24 | said_list = list(zip(spans, numbers)) 25 | said = '' 26 | for pair in said_list: 27 | said += pair[0] 28 | said += pair[1] 29 | 30 | return said 31 | 32 | with open('inputs/day_10.txt', 'r') as f: 33 | puzzle_input = f.read().strip() 34 | 35 | look = puzzle_input 36 | cycles = 0 37 | while cycles < 50: # 40 for Answer One 50 for Answer Two 38 | new_number = say(look) 39 | look = new_number 40 | cycles += 1 41 | 42 | print(len(new_number)) 43 | -------------------------------------------------------------------------------- /2015/day_20.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 20 - Infinite Elves and Infinite Houses""" 2 | 3 | 4 | def find_factors(number): 5 | """Returns the prime factorisation of a number.""" 6 | factors = set() 7 | for i in range(1, int(number**0.5) + 1): 8 | if number % i == 0: 9 | factors.add(i) 10 | factors.add(number // i) 11 | 12 | return factors 13 | 14 | 15 | present_goal = 29000000 16 | door = 1 17 | while True: 18 | factors = find_factors(door) 19 | presents = sum([n * 10 for n in factors]) 20 | if presents >= present_goal: 21 | break 22 | door += 1 23 | 24 | # Answer One 25 | print("Lowest Numbered House =", door) 26 | 27 | factor_dict = {} 28 | door = 1 29 | while True: 30 | factors = find_factors(door) 31 | for factor in factors: 32 | factor_dict.setdefault(factor, 0) 33 | factor_dict[factor] += 1 34 | presents = sum([n * 11 for n in factors if factor_dict[n] <= 50]) 35 | if presents >= present_goal: 36 | break 37 | door += 1 38 | 39 | # Answer Two 40 | print("Lowest With 50 Limit =", door) 41 | -------------------------------------------------------------------------------- /2015/day_23.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 23 - Opening the Turing Lock""" 2 | 3 | with open('inputs/day_23.txt') as f: 4 | instructions = [line.strip() for line in f] 5 | 6 | registers = {'a': 1, 'b': 0} # Change a to 0 for Part One 7 | 8 | pos = 0 9 | while pos < len(instructions): 10 | parsed = instructions[pos].split(' ') 11 | if len(parsed) == 3: 12 | if parsed[0] == 'jio': 13 | if registers[parsed[1].strip(',')] == 1: 14 | pos += int(parsed[2]) 15 | continue 16 | 17 | elif parsed[0] == 'jie': 18 | if registers[parsed[1].strip(',')] % 2 == 0: 19 | pos += int(parsed[2]) 20 | continue 21 | 22 | elif parsed[0] == 'hlf': 23 | registers[parsed[1]] /= 2 24 | 25 | elif parsed[0] == 'tpl': 26 | registers[parsed[1]] *= 3 27 | 28 | elif parsed[0] == 'inc': 29 | registers[parsed[1]] += 1 30 | 31 | elif parsed[0] == 'jmp': 32 | pos += int(parsed[1]) 33 | continue 34 | 35 | pos += 1 36 | 37 | print("Value of Register b =", registers['b']) 38 | -------------------------------------------------------------------------------- /2019/day_04.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 4 - Secure Container.""" 2 | 3 | 4 | def number_matching_criteria(part_two=False): 5 | """Return number of numbers mathching criteria.""" 6 | num_matched = 0 7 | for n in range(145852, 616943): 8 | digits = [int(x) for x in list(str(n))] 9 | adjacent = False 10 | increase = False 11 | if digits == sorted(digits): 12 | increase = True 13 | else: 14 | pass 15 | 16 | for digit in digits: 17 | if part_two: 18 | if digits.count(digit) == 2: 19 | adjacent = True 20 | break 21 | else: 22 | if digits.count(digit) > 1: 23 | adjacent = True 24 | break 25 | 26 | if adjacent and increase: 27 | num_matched += 1 28 | 29 | return num_matched 30 | 31 | 32 | # Answer One 33 | print("Matches under first criteria:", number_matching_criteria()) 34 | 35 | # Answer Two 36 | print("Matches under second criteria:", number_matching_criteria(True)) 37 | -------------------------------------------------------------------------------- /2016/day_19.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 19 - An Elephant Named Joseph""" 2 | 3 | import collections 4 | 5 | def elf_exchange(elves): 6 | """Simulate across from eliminations in a circle with arg no. of points.""" 7 | left = collections.deque() 8 | right = collections.deque() 9 | for i in range(1, elves + 1): 10 | # Split around the middle 11 | if i < (elves // 2) + 1: 12 | left.append(i) 13 | else: 14 | right.append(i) 15 | 16 | while left and right: 17 | # If odd pop rounded down middle (left) else pop middle (right) 18 | if len(left) > len(right): 19 | left.pop() 20 | else: 21 | right.popleft() 22 | 23 | # Rotate the middle split 24 | right.append(left.popleft()) 25 | left.append(right.popleft()) 26 | 27 | return left[0] 28 | 29 | 30 | num_elves = 3005290 31 | 32 | # Answer One - Uses binary 'trick' related to the Josephus problem 33 | print("The winning elf:", int(bin(num_elves)[3:] + '1', 2)) 34 | 35 | # Answer Two 36 | print("The new winning elf:", elf_exchange(num_elves)) 37 | -------------------------------------------------------------------------------- /2016/day_16.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 16 - Dragon Checksum""" 2 | 3 | 4 | def dragon(binary): 5 | """Apply a modified dragon curve transformation and returns the result.""" 6 | dragoned = binary + '0' 7 | for char in binary[-1::-1]: 8 | if char == '1': 9 | dragoned += '0' 10 | else: 11 | dragoned += '1' 12 | return dragoned 13 | 14 | 15 | def calc_checksum(to_sum): 16 | """Calculate the checksum of a string and return it.""" 17 | i = 0 18 | summed = '' 19 | while i < len(to_sum) - 1: 20 | if to_sum[i] == to_sum[i+1]: 21 | summed += '1' 22 | else: 23 | summed += '0' 24 | 25 | i += 2 26 | 27 | return summed 28 | 29 | 30 | to_fill = 35651584 # Change to 272 for part one 31 | data = '01111010110010011' 32 | 33 | while len(data) < to_fill: 34 | data = dragon(data) 35 | 36 | checksum = data[:to_fill] 37 | 38 | while True: 39 | checksum = calc_checksum(checksum) 40 | if len(checksum) % 2 == 1: 41 | break 42 | 43 | # Answer One / Answer Two 44 | print("Checksum:", checksum) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 IFinners 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /2017/day_13.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 13 - Packet Scanners""" 2 | 3 | 4 | def severity(layers, delay): 5 | """Calculate cost of getting caught when traversing a Firewall.""" 6 | layers_list = layers.strip().split('\n') 7 | 8 | severity = 0 9 | for layer in layers_list: 10 | layer_parts = (str(layer).split(':')) 11 | layer_num = int(layer_parts[0]) 12 | layer_range = int(str(layer_parts[1]).strip()) 13 | 14 | if (layer_num + delay) % ((layer_range - 1) * 2) == 0: 15 | severity += layer_num * layer_range 16 | if layer_num == 0: 17 | severity += 1 # Prevents being caught on 0 not counting 18 | 19 | if delay == 0: 20 | print('Bypassing with no delay results in overall severity of ' 21 | + str(severity - 1)) 22 | 23 | if severity == 0: 24 | return delay 25 | 26 | 27 | with open('inputs/day_13.txt') as f: 28 | firewall = f.read() 29 | 30 | delay = 0 31 | while severity(firewall, delay) is None: 32 | delay += 1 33 | 34 | # Answer One 35 | print('The smallest delay that would bypass the firewall is:', delay, 'picoseconds.') 36 | -------------------------------------------------------------------------------- /2021/day_02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 2 - Dive!""" 4 | 5 | 6 | with open('inputs/day_02.txt', 'r') as aoc_input: 7 | input_lines = [line.strip().split(' ') for line in aoc_input.readlines()] 8 | input_lines = [(x, int(y)) for [x, y] in input_lines] 9 | 10 | horizontal_position = 0 11 | depth = 0 12 | 13 | for move, amount in input_lines: 14 | if move == 'forward': 15 | horizontal_position += amount 16 | elif move == 'up': 17 | depth -= amount 18 | elif move == 'down': 19 | depth += amount 20 | 21 | # Answer One 22 | print("Product of final horizontal position and final depth:", 23 | horizontal_position * depth) 24 | 25 | 26 | horizontal_position = 0 27 | depth = 0 28 | aim = 0 29 | 30 | for move, amount in input_lines: 31 | if move == 'forward': 32 | horizontal_position += amount 33 | depth += aim * amount 34 | elif move == 'up': 35 | aim -= amount 36 | elif move == 'down': 37 | aim += amount 38 | 39 | # Answer Two 40 | print("Product of final horizontal position and final depth:", 41 | horizontal_position * depth) 42 | -------------------------------------------------------------------------------- /2020/day_02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 02 - Password Philosophy.""" 4 | 5 | import re 6 | 7 | 8 | with open ('inputs/day_02.txt', 'r') as password_database: 9 | lines = [line for line in password_database.readlines()] 10 | 11 | line_regex = re.compile('(\d*)-(\d*)\s(\w):\s(\w*)') 12 | part_one_valid = 0 13 | part_two_valid = 0 14 | for line in lines: 15 | first_num, second_num, char, password = line_regex.findall(line)[0] 16 | first_num = int(first_num) 17 | second_num = int(second_num) 18 | 19 | # Part One 20 | char_count = password.count(char) 21 | if char_count >= first_num and char_count <= second_num: 22 | part_one_valid += 1 23 | 24 | # Part Two 25 | if password[first_num - 1] == char and password[second_num - 1] != char: 26 | part_two_valid += 1 27 | elif password[first_num - 1] != char and password[second_num - 1] == char: 28 | part_two_valid += 1 29 | 30 | # Answer One 31 | print("Number of valid passwords according to first policy:", part_one_valid) 32 | 33 | # Answer Two 34 | print("Number of valid passwords according to second policy:", part_two_valid) 35 | -------------------------------------------------------------------------------- /2022/day_06/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | file, err := os.ReadFile("input.txt") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | datastream := string(file) 17 | answerOne, err := uniqueLettersInWindow(datastream, 4) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | fmt.Printf("Answer One: %d\n", answerOne) 22 | 23 | answerTwo, err := uniqueLettersInWindow(datastream, 14) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | fmt.Printf("Answer Two: %d\n", answerTwo) 28 | } 29 | 30 | func uniqueLettersInWindow(datastream string, windowSize int) (int, error) { 31 | lowIndex := 0 32 | highIndex := windowSize - 1 33 | datastreamLength := len(datastream) 34 | for highIndex < datastreamLength { 35 | windowStart: 36 | for i := lowIndex; i <= highIndex; i++ { 37 | for j := i + 1; j <= highIndex; j++ { 38 | if datastream[i] == datastream[j] { 39 | lowIndex = i + 1 40 | highIndex = i + windowSize 41 | continue windowStart 42 | } 43 | } 44 | } 45 | return highIndex + 1, nil 46 | } 47 | return -1, errors.New("Failed to find unique window") 48 | } 49 | -------------------------------------------------------------------------------- /2019/day_06.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 6 - Universal Orbit Map.""" 2 | 3 | 4 | with open('inputs/day_06.txt', 'r') as f: 5 | orbits = f.read().split() 6 | 7 | objects_dict = {} 8 | for orbit in orbits: 9 | orbited, orbiter = orbit.split(')') 10 | objects_dict[orbiter] = orbited 11 | 12 | num_orbits = 0 13 | for orbiter in objects_dict: 14 | next_orbit = objects_dict.get(orbiter, None) 15 | while next_orbit: 16 | next_orbit = objects_dict.get(next_orbit, None) 17 | num_orbits += 1 18 | 19 | # Answer One 20 | print("Number of direct and indirect orbits:", num_orbits) 21 | 22 | you_path = {} 23 | on_you_path = set() 24 | transfers = 0 25 | next_orbit = objects_dict.get("YOU", None) 26 | while next_orbit: 27 | transfers += 1 28 | you_path[next_orbit] = transfers 29 | on_you_path.add(next_orbit) 30 | next_orbit = objects_dict.get(next_orbit, None) 31 | 32 | transfers = 0 33 | next_orbit = objects_dict.get("SAN", None) 34 | while next_orbit and next_orbit not in on_you_path: 35 | transfers += 1 36 | next_orbit = objects_dict.get(next_orbit, None) 37 | 38 | # Answer Two 39 | print("Transfers between you and Santa:", transfers + you_path[next_orbit] - 1) 40 | -------------------------------------------------------------------------------- /2016/day_05.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 5 - How About a Nice Game of Chess?""" 2 | 3 | import hashlib 4 | 5 | 6 | def find_pass(door_id, part_two=False): 7 | """Find password by finding significant hashed then hexed values.""" 8 | if part_two: 9 | password = ['', '', '', '', '', '', '', ''] 10 | else: 11 | password = '' 12 | number = 0 13 | found = 0 14 | while found < 8: 15 | hexed = hashlib.md5('{}{}'.format(door_id, number).encode('utf-8')).hexdigest() 16 | if str(hexed)[0:5] == '00000': 17 | if part_two: 18 | pos = hexed[5] 19 | if pos.isnumeric() and int(pos) < 8 and password[int(pos)] == '': 20 | password[int(pos)] = str(hexed[6]) 21 | found += 1 22 | else: 23 | password += str(hexed[5]) 24 | found += 1 25 | 26 | number += 1 27 | 28 | if part_two: 29 | return ''.join(password) 30 | 31 | return password 32 | 33 | 34 | door_id = 'ojvtpuvg' 35 | 36 | # Answer One 37 | print("First Door Password:", find_pass(door_id)) 38 | 39 | # Answer Two 40 | print("Second Door Password:", find_pass(door_id, part_two=True)) 41 | -------------------------------------------------------------------------------- /2018/day_14.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 14 - Chocolate Charts""" 2 | 3 | 4 | part_one, part_two = False, False 5 | to_make = '323081' 6 | make_list = [int(x) for x in to_make] 7 | found = 0 8 | recipies = [3, 7] 9 | num_recipies = 2 10 | elf_1, elf_2 = 0, 1 11 | while not part_one or not part_two: 12 | new = recipies[elf_1] + recipies[elf_2] 13 | if new < 10: 14 | new = (new,) 15 | else: 16 | new = divmod(new, 10) 17 | 18 | for recipie in new: 19 | recipies.append(recipie) 20 | num_recipies += 1 21 | if recipie == make_list[found]: 22 | found += 1 23 | if found == len(make_list): 24 | # Answer Two 25 | print("Recipies before:", num_recipies - len(to_make)) 26 | part_two = True 27 | break 28 | else: 29 | found = 1 if recipie == make_list[0] else 0 30 | 31 | elf_1 = (elf_1 + recipies[elf_1] + 1) % num_recipies 32 | elf_2 = (elf_2 + recipies[elf_2] + 1) % num_recipies 33 | 34 | # Answer One 35 | if not part_one: 36 | if num_recipies == int(to_make) + 10: 37 | last_10 = ''.join([str(x) for x in recipies[-10:]]) 38 | print("10 Recipies after:", last_10) 39 | part_one = True 40 | -------------------------------------------------------------------------------- /2018/day_05.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 5 - Alchemical Reduction""" 2 | 3 | 4 | def react_polymer(polymer): 5 | """Return polymer after all unit reactions have taken place.""" 6 | prev_poly = None 7 | while prev_poly != polymer: 8 | prev_poly = polymer 9 | for letter in 'abcdefghijklmnopqrstuvwxyz': 10 | upper = letter.upper() 11 | polymer = polymer.replace('{}{}'.format(letter, upper), '') 12 | polymer = polymer.replace('{}{}'.format(upper, letter), '') 13 | 14 | return polymer 15 | 16 | 17 | if __name__ == '__main__': 18 | 19 | with open('inputs/day_05.txt') as f: 20 | polymer = f.read() 21 | 22 | reduced_polymer = react_polymer(polymer) 23 | reacted_length = len(reduced_polymer) 24 | 25 | # Answer One 26 | print("Length after reduction:", reacted_length) 27 | 28 | shortest = reacted_length 29 | for letter in 'abcdefghijklmnopqrstuvwxyz': 30 | new_polymer = reduced_polymer.replace(letter, '') 31 | new_polymer = new_polymer.replace(letter.upper(), '') 32 | 33 | reacted_length = len(react_polymer(new_polymer)) 34 | if reacted_length < shortest: 35 | shortest = reacted_length 36 | 37 | # Answer Two 38 | print("Shortest polymer:", shortest) 39 | -------------------------------------------------------------------------------- /2015/day_11.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 11 - Corporate Policy""" 2 | 3 | import re 4 | 5 | 6 | puzzle_input = 'hxbxwxba' # Change to Part One Solution for Part Two 7 | old_pass = puzzle_input 8 | old_rev = list(reversed(old_pass)) 9 | alphabet = 'abcdefghijklmnopqrstuvwxyz' 10 | valid = False 11 | 12 | while not valid: 13 | for pos, letter in enumerate(old_rev): 14 | if letter == 'z': 15 | old_rev[pos] = 'a' 16 | else: 17 | old_rev[pos] = alphabet[alphabet.find(letter) + 1] 18 | new_pass = ''.join(old_rev)[-1::-1] 19 | break 20 | 21 | # Check Validity 22 | confusing_regex = re.compile(r'i|o|l') 23 | if confusing_regex.search(new_pass): 24 | continue 25 | 26 | doubles_regex = re.compile(r'(.)\1') 27 | if len(doubles_regex.findall(new_pass)) < 2: 28 | continue 29 | 30 | straight = False 31 | for pos, letter in enumerate(new_pass[:-2]): 32 | if (new_pass[pos] not in ('y', 'z') 33 | and new_pass[pos + 1] == alphabet[alphabet.find(letter) + 1] 34 | and new_pass[pos + 2] == alphabet[alphabet.find(letter) + 2]): 35 | 36 | straight = True 37 | 38 | if not straight: 39 | continue 40 | 41 | valid = True 42 | 43 | print("New Password =",new_pass) 44 | -------------------------------------------------------------------------------- /2022/day_08/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image" 7 | "log" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | file, err := os.Open("input.txt") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | scanner := bufio.NewScanner(file) 17 | trees := map[image.Point]rune{} 18 | y := 0 19 | for scanner.Scan() { 20 | row := scanner.Text() 21 | for x, height := range row { 22 | trees[image.Point{x, y}] = height 23 | } 24 | y++ 25 | } 26 | 27 | visible := 0 28 | highestScenicScore := 0 29 | deltas := []image.Point{{0, -1}, {0, 1}, {-1, 0}, {1, 0}} 30 | for coords, treeHeight := range trees { 31 | treeVisible := false 32 | scenicScore := 1 33 | for _, delta := range deltas { 34 | for i := 1; ; i++ { 35 | newCoords := coords.Add(delta.Mul(i)) 36 | newTreeHeight, ok := trees[newCoords] 37 | if !ok { 38 | treeVisible = true 39 | scenicScore *= i - 1 40 | break 41 | } 42 | if newTreeHeight >= treeHeight { 43 | scenicScore *= i 44 | break 45 | } 46 | } 47 | } 48 | 49 | if treeVisible { 50 | visible++ 51 | } 52 | 53 | if scenicScore > highestScenicScore { 54 | highestScenicScore = scenicScore 55 | } 56 | } 57 | 58 | fmt.Println("Answer One", visible) 59 | 60 | fmt.Println("Answer Two", highestScenicScore) 61 | } 62 | -------------------------------------------------------------------------------- /2015/day_25.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 25 - Let It Snow""" 2 | 3 | 4 | def find_code(code, row, column): 5 | first = True 6 | while coords['row'] != row or coords['col'] != column: 7 | 8 | if first: 9 | coords['highest_row'] += 1 10 | coords['row'] = coords['highest_row'] 11 | coords['col'] = 1 12 | code = (code * 252533) % 33554393 13 | first = False 14 | 15 | else: 16 | mid_fill = coords['highest_col'] - 2 17 | while mid_fill >= 0: 18 | coords['row'] -= 1 19 | coords['col'] += 1 20 | code = (code * 252533) % 33554393 21 | mid_fill -= 1 22 | 23 | if coords['row'] == row and coords['col'] == column: 24 | return code 25 | 26 | coords['row'] -= 1 27 | coords['col'] += 1 28 | code = (code * 252533) % 33554393 29 | 30 | if coords['row'] == row and coords['col'] == column: 31 | return code 32 | 33 | coords['highest_col'] += 1 34 | 35 | first = True 36 | 37 | return code 38 | 39 | 40 | coords = {'row': 1, 'col': 1, 'highest_row': 1, 'highest_col': 1} 41 | 42 | # Answer One 43 | print("Weather Machine Code =", find_code(20151125, 2981, 3075)) 44 | -------------------------------------------------------------------------------- /2015/day_02.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 2 - I Was Told There Would Be No Math""" 2 | 3 | def calculate_paper(): 4 | """Calculates and Prints Amount of Paper Required to Wrap Presents.""" 5 | square_feet = 0 6 | for line in areas: 7 | length, width, height = line.split('x') 8 | sides_areas = [] 9 | sides_areas.append(2 * int(length) * int(width)) 10 | sides_areas.append(2 * int(width) * int(height)) 11 | sides_areas.append(2 * int(height) * int(length)) 12 | 13 | slack = min(sides_areas) // 2 14 | 15 | square_feet += slack 16 | for area in sides_areas: 17 | square_feet += area 18 | 19 | print(square_feet) 20 | 21 | 22 | def calculate_ribbon(): 23 | """Calculates and Prints Amount of Ribbon Required.""" 24 | feet = 0 25 | for line in areas: 26 | side_lengths = line.split('x') 27 | int_list = sorted([int(x) for x in side_lengths]) 28 | smallest_face = int_list[0] * 2 + int_list[1] * 2 29 | volume = int_list[0] * int_list[1] * int_list[2] 30 | feet += smallest_face 31 | feet += volume 32 | 33 | print(feet) 34 | 35 | 36 | with open('inputs/day_02.txt') as f: 37 | areas = f.readlines() 38 | 39 | 40 | # Answer Part One 41 | calculate_paper() 42 | 43 | # Answer Part Two 44 | calculate_ribbon() 45 | -------------------------------------------------------------------------------- /2017/day_09.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 9 - Stream Processing""" 2 | 3 | 4 | def clean(stream): 5 | """Filter garbage and find total 'score' of all group in a given input.""" 6 | # Open input file 7 | stream = stream.read() 8 | 9 | # Set up logic for moving through text and evaluating situation 10 | total = 0 11 | cleaned = 0 12 | skip = False 13 | level = 0 14 | garbage = False 15 | 16 | for character in stream: 17 | # If currently within a garbage phase i.e. < previously reached 18 | if garbage: 19 | if skip: 20 | skip = False 21 | 22 | elif character == '!': 23 | skip = True 24 | 25 | elif character == '>': 26 | garbage = False 27 | 28 | else: 29 | cleaned += 1 30 | 31 | # Not in garbage phase so looking for depth of groups 32 | else: 33 | if character == '{': 34 | level += 1 35 | 36 | elif character == '}': 37 | total += level 38 | level -= 1 39 | 40 | elif character == '<': 41 | garbage = True 42 | 43 | print('Answer One:', total) 44 | print('Answer Two:', cleaned) 45 | 46 | 47 | with open('inputs/day_09.txt') as f: 48 | stream = f.read() 49 | clean(stream) 50 | -------------------------------------------------------------------------------- /2021/day_06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 6 - Lanternfish""" 4 | 5 | 6 | with open('inputs/day_06.txt','r') as aoc_input: 7 | lanternfish_initial = [int(x) for x in aoc_input.read().split(',')] 8 | 9 | lanternfish = lanternfish_initial.copy() 10 | for _ in range(80): 11 | lanternfish = [x - 1 for x in lanternfish] 12 | 13 | i = len(lanternfish) - 1 14 | while i >= 0: 15 | if lanternfish[i] == -1: 16 | lanternfish[i] = 6 17 | lanternfish.append(8) 18 | 19 | i -= 1 20 | 21 | # Answer One 22 | print("Number of Lanternfish after 80 days:", len(lanternfish)) 23 | 24 | lanternfish_dict = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} 25 | for fish in lanternfish_initial: 26 | lanternfish_dict[fish] += 1 27 | 28 | for _ in range(256): 29 | new_fish_dict = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} 30 | for key in lanternfish_dict.keys(): 31 | new_fish_dict[key - 1] = lanternfish_dict[key] 32 | 33 | for key in new_fish_dict: 34 | if key == -1: 35 | new_fish_dict[8] = new_fish_dict[-1] 36 | new_fish_dict[6] += new_fish_dict[-1] 37 | 38 | lanternfish_dict = new_fish_dict 39 | del lanternfish_dict[-1] 40 | 41 | # Answer Two 42 | print("Number of Lanternfish after 256 days:", sum(lanternfish_dict.values())) 43 | -------------------------------------------------------------------------------- /2022/day_01/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strconv" 9 | "slices" 10 | ) 11 | 12 | func getCalorieTotals(lines []string) []int { 13 | if len(lines) == 0 { 14 | return []int{} 15 | } 16 | totalCalories := []int{0} 17 | 18 | numElves := 0 19 | for _, line := range lines { 20 | if line == "" { 21 | totalCalories = append(totalCalories, 0) 22 | numElves += 1 23 | } 24 | calories, err := strconv.Atoi(line) 25 | if err == nil { 26 | totalCalories[numElves] += calories 27 | } 28 | } 29 | 30 | return totalCalories 31 | } 32 | 33 | func main() { 34 | file, err := os.Open("input.txt") 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | defer file.Close() 39 | 40 | lines := []string{} 41 | scanner := bufio.NewScanner(file) 42 | for scanner.Scan() { 43 | lines = append(lines, scanner.Text()) 44 | } 45 | if err := scanner.Err(); err != nil { 46 | log.Fatal(err) 47 | } 48 | 49 | calorieTotals := getCalorieTotals(lines) 50 | slices.Sort(calorieTotals) 51 | 52 | fmt.Printf("Answer One: %d\n", calorieTotals[len(calorieTotals)-1]) 53 | 54 | topThreeCalorieTotals := calorieTotals[len(calorieTotals)-3:] 55 | topThreeSum := 0 56 | for _, calories := range topThreeCalorieTotals { 57 | topThreeSum += calories 58 | } 59 | fmt.Printf("Answer Two: %d\n", topThreeSum) 60 | } 61 | -------------------------------------------------------------------------------- /2016/day_03.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 3 - Squares With Three Sides""" 2 | 3 | import re 4 | 5 | def check_triangle(lengths): 6 | """Check if 3 lengths form a triangle - sum of smallest two > than other.""" 7 | in_order = sorted([int(x) for x in lengths]) 8 | smallest_sum = in_order[0] + in_order[1] 9 | if smallest_sum > in_order[2]: 10 | return True 11 | 12 | 13 | with open('inputs/day_03.txt') as f: 14 | sides_list = [line.strip() for line in f.readlines()] 15 | 16 | triangles = 0 17 | for trio in sides_list: 18 | if check_triangle(re.findall(r'(\d+)', trio)): 19 | triangles += 1 20 | 21 | # Answer Part One 22 | print("Number of Valid Triangles =", triangles) 23 | 24 | 25 | # Part Two 26 | a = [] 27 | b = [] 28 | c = [] 29 | # Extract side lengths and group lines into three so real trios are lined up 30 | i = 0 31 | while i < len(sides_list): 32 | a.append(re.findall(r'(\d+)', sides_list[i])) 33 | b.append(re.findall(r'(\d+)', sides_list[i + 1])) 34 | c.append(re.findall(r'(\d+)', sides_list[i + 2])) 35 | 36 | i += 3 37 | 38 | triangles = 0 39 | i = 0 40 | while i < len(a): 41 | j = 0 42 | while j < 3: 43 | if check_triangle([a[i][j], b[i][j], c[i][j]]): 44 | triangles += 1 45 | j += 1 46 | 47 | i += 1 48 | 49 | # Answer Part Two 50 | print("Number of Valid Vertical Triangles", triangles) 51 | -------------------------------------------------------------------------------- /2017/day_04.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 4 - High-Entropy Passphrases""" 2 | 3 | 4 | # Open list, read it and split by newline 5 | pass_txt = open('inputs/day_04.txt') 6 | lines = pass_txt.read() 7 | pass_list = lines.split('\n') 8 | 9 | 10 | def dupe_check(passphrase): 11 | """Return only if input has no duplicate words in it.""" 12 | words = passphrase.split(' ') 13 | unique = set(words) 14 | if words != ['']: 15 | return len(words) == len(unique) 16 | 17 | 18 | def anagram_check(passphrase): 19 | """Return only if input has no anagram pairs in it.""" 20 | words = passphrase.split(' ') 21 | word_list = [] 22 | for word in words: 23 | # Make all words have their letters in alphabetical order 24 | letters = list(word) 25 | ordered = ('').join(sorted(letters)) 26 | word_list.append(ordered) 27 | unique = set(word_list) 28 | if words != ['']: 29 | return len(words) == len(unique) 30 | 31 | 32 | # Answer One 33 | dupeless = 0 34 | for passphrase in pass_list: 35 | if dupe_check(passphrase): 36 | dupeless += 1 37 | 38 | print("Number of passwords without duplicates:", dupeless) 39 | 40 | # Answer Two 41 | anagramless = 0 42 | for passphrase in pass_list: 43 | if anagram_check(passphrase): 44 | anagramless += 1 45 | 46 | print("Number of passwords without anagram pairs:", anagramless) 47 | -------------------------------------------------------------------------------- /2020/day_09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 09 - Encoding Error.""" 4 | 5 | 6 | from itertools import combinations 7 | 8 | 9 | with open ('inputs/day_09.txt', 'r') as xmas: 10 | numbers = [int(number.strip()) for number in xmas.readlines()] 11 | 12 | for index, number in enumerate(numbers[25:], 25): 13 | prev_25 = numbers[index - 25 : index] 14 | valid_next = set([sum(combo) for combo in combinations(prev_25, 2)]) 15 | if number not in valid_next: 16 | break 17 | 18 | # Answer One 19 | print("First number that is not the sum of two numbers in the previous 25:", 20 | numbers[index]) 21 | 22 | target_number = numbers[index] 23 | weakness_found = False 24 | starting_index = 0 25 | while True: 26 | current_sum = summed_nums = 0 27 | for number in numbers[starting_index:]: 28 | current_sum += number 29 | summed_nums += 1 30 | if current_sum > target_number: 31 | break 32 | if current_sum == target_number: 33 | weakness_found = True 34 | break 35 | 36 | if weakness_found: 37 | break 38 | 39 | starting_index += 1 40 | 41 | contiguous_nums = numbers[starting_index: starting_index + summed_nums] 42 | encryption_weakness = min(contiguous_nums) + max(contiguous_nums) 43 | 44 | # Answer Two 45 | print("Encryption weakness:", encryption_weakness) 46 | -------------------------------------------------------------------------------- /2016/day_01.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 1 - No Time for a Taxicab""" 2 | 3 | 4 | def grid_walk(directions, part_two=False): 5 | """Return Manhattan distance of destination or first revisited place.""" 6 | x = 0 7 | y = 0 8 | orientation = 0 9 | visited = [[0, 0],] 10 | for direction in directions: 11 | turn = direction[0] 12 | steps = int(direction[1:]) 13 | 14 | if turn == 'R': 15 | orientation = (orientation + 1) % 4 16 | else: 17 | orientation = (orientation - 1) % 4 18 | 19 | for _ in range(steps): 20 | 21 | if orientation == 0: 22 | y += 1 23 | elif orientation == 1: 24 | x += 1 25 | elif orientation == 2: 26 | y -= 1 27 | else: 28 | x -= 1 29 | 30 | if part_two: 31 | if [x, y] in visited: 32 | return abs(x) + abs(y) 33 | else: 34 | visited.append([x, y]) 35 | 36 | return abs(x) + abs(y) 37 | 38 | 39 | with open('inputs/day_01.txt') as f: 40 | instructions = f.read().split(', ') 41 | 42 | # Answer Part One 43 | print("Easter Bunny HQ is", grid_walk(instructions), "Blocks Away.") 44 | 45 | # Answer Part Two 46 | print("First Revisited House is", grid_walk(instructions, part_two=True), "Blocks Away.") 47 | -------------------------------------------------------------------------------- /2021/day_13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 13 - Transparent Origami""" 4 | 5 | 6 | with open('inputs/day_13.txt', 'r') as aoc_input: 7 | dots, folds = aoc_input.read().split('\n\n') 8 | 9 | dot_dict = {} 10 | for dot in dots.split('\n'): 11 | x, y = dot.strip().split(',') 12 | dot_dict[(int(x), int(y))] = '#' 13 | 14 | part_one = False 15 | for fold in folds.strip().split('\n'): 16 | axis, index = fold.strip('fold along ').split('=') 17 | index = int(index) 18 | 19 | folded = {} 20 | for dot in dot_dict: 21 | x, y = dot 22 | 23 | if axis == 'y': 24 | new_y = index + (index - y) if y > index else y 25 | folded[(x, new_y)] = '#' 26 | 27 | elif axis == 'x': 28 | new_x = index + (index - x) if x > index else x 29 | folded[(new_x, y)] = '#' 30 | 31 | if not part_one: 32 | # Answer One 33 | print("Number of dots visible after first fold:", len(folded)) 34 | part_one = True 35 | 36 | dot_dict = folded 37 | 38 | max_x = max(dot_dict.keys())[0] 39 | max_y = max(dot_dict.keys(), key=lambda x: x[1])[1] 40 | 41 | # Answer Two 42 | print("Eight letter code to activate the thermal imaging system:", end='\n\n') 43 | for y in range(max_y + 1): 44 | row = '' 45 | for x in range(max_x + 1): 46 | row += dot_dict.get((x, y), ' ') 47 | print(row) 48 | print() 49 | -------------------------------------------------------------------------------- /2016/day_18.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 18 - Like a Rogue""" 2 | 3 | with open('inputs/day_18.txt') as f: 4 | start = f.read().strip() 5 | 6 | def map_traps(prev_pat, row_limit): 7 | """Work out the number of safe tiles in a trapped room.""" 8 | safe = prev_pat.count('.') 9 | row = 1 10 | while row < row_limit: 11 | next_row = '' 12 | trap_patterns = ('^^.', '.^^', '^..', '..^') 13 | 14 | pattern = '.{}{}'.format(prev_pat[0], prev_pat[1]) 15 | if pattern in trap_patterns: 16 | next_row += '^' 17 | else: 18 | next_row += '.' 19 | 20 | i = 1 21 | while i < len(prev_pat) - 1: 22 | pattern = '{}{}{}'.format(prev_pat[i-1], prev_pat[i], prev_pat[i+1]) 23 | if pattern in trap_patterns: 24 | next_row += '^' 25 | else: 26 | next_row += '.' 27 | 28 | i += 1 29 | 30 | pattern = '{}{}.'.format(prev_pat[i-1], prev_pat[1+1]) 31 | if pattern in trap_patterns: 32 | next_row += '^' 33 | else: 34 | next_row += '.' 35 | 36 | safe += next_row.count('.') 37 | prev_pat = next_row 38 | row += 1 39 | 40 | return safe 41 | 42 | 43 | # Answer One 44 | print("Safe tiles within 40 rows:", map_traps(start, 40)) 45 | 46 | # Answer Two 47 | print("Safe tiles within 400000 rows:", map_traps(start, 400000)) 48 | -------------------------------------------------------------------------------- /2015/day_08.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 8 - Matchsticks""" 2 | 3 | 4 | def literal_memory(lines): 5 | literal = 0 6 | memory = 0 7 | for line in lines: 8 | # Remove outer " 9 | line = line.strip()[1:-1] 10 | literal += len(line) + 2 11 | line_memory = 0 12 | 13 | i = 0 14 | while i < len(line): 15 | line_memory += 1 16 | # Skip over the escaped characters by the appropriate number 17 | if line[i] == '\\': 18 | if line[i + 1] == 'x': 19 | i += 4 20 | else: 21 | i += 2 22 | else: 23 | i += 1 24 | 25 | memory += line_memory 26 | 27 | return literal - memory 28 | 29 | 30 | def encoded_literal(lines): 31 | literal = 0 32 | encoded = 0 33 | for line in lines: 34 | literal += len(line) 35 | 36 | # Starts at 2 due to the outer " needed to be encoded 37 | line_encoded = 2 38 | for character in line: 39 | if character in ('\\', '"'): 40 | line_encoded += 2 41 | else: 42 | line_encoded += 1 43 | 44 | encoded += line_encoded 45 | 46 | return encoded - literal 47 | 48 | 49 | with open('inputs/day_08.txt') as f: 50 | lines_list = f.readlines() 51 | 52 | # Answer One 53 | print("Answer One =", literal_memory(lines_list)) 54 | 55 | # Answer Two 56 | print("Answer Two =", encoded_literal(lines_list)) 57 | -------------------------------------------------------------------------------- /2017/day_12.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 12 - Digital Plumber""" 2 | 3 | import re 4 | 5 | 6 | def make_dict(): 7 | """Create a dictionary of pipes and their connections.""" 8 | with open('inputs/day_12.txt') as f: 9 | survey = [line for line in f.readlines()] 10 | pipes = {} 11 | for info in survey: 12 | pipe, *connections = re.findall(r'\d+', info) 13 | pipes[pipe] = connections 14 | 15 | return pipes 16 | 17 | 18 | def find_groups(pipe_list, part_two=False): 19 | """Use connection information to form and count groups.""" 20 | groups = 0 21 | while pipe_list: 22 | to_process = [] 23 | to_process.append(pipe_list[0]) 24 | members = set() 25 | while to_process: 26 | connects = pipes[to_process.pop()] 27 | for connect in connects: 28 | if connect not in members: 29 | to_process.append(connect) 30 | members.add(connect) 31 | 32 | if not part_two and '0' in members: 33 | return len(members) 34 | [pipe_list.remove(member) for member in members] 35 | groups += 1 36 | 37 | return groups 38 | 39 | 40 | if __name__ == '__main__': 41 | 42 | pipes = make_dict() 43 | pipe_list = [pipe for pipe in pipes] 44 | 45 | # Answer One 46 | print("Number of programs in group containing 0:", find_groups(pipe_list)) 47 | 48 | # Answer Two 49 | print("Number of groups:", find_groups(pipe_list, part_two=True)) 50 | -------------------------------------------------------------------------------- /2021/day_25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 25 - Sea Cucumber""" 4 | 5 | 6 | with open('inputs/day_25.txt', 'r') as aoc_input: 7 | lines = [line.strip() for line in aoc_input.readlines()] 8 | 9 | east_cucumbers = set() 10 | south_cucumbers = set() 11 | for y, row in enumerate(lines): 12 | for x, value in enumerate(row): 13 | if value == '>': 14 | east_cucumbers.add((x, y)) 15 | elif value == 'v': 16 | south_cucumbers.add((x, y)) 17 | 18 | steps = 0 19 | while True: 20 | new_east = set() 21 | new_south = set() 22 | 23 | for coords in east_cucumbers: 24 | 25 | x, y = coords 26 | forward_x = (x + 1) % len(lines[0]) 27 | forward = (forward_x, y) 28 | 29 | if forward not in east_cucumbers and forward not in south_cucumbers: 30 | new_east.add(forward) 31 | else: 32 | new_east.add(coords) 33 | 34 | for coords in south_cucumbers: 35 | 36 | x, y = coords 37 | forward_y = (y + 1) % len(lines) 38 | forward = (x, forward_y) 39 | 40 | if forward not in new_east and forward not in south_cucumbers: 41 | new_south.add(forward) 42 | else: 43 | new_south.add(coords) 44 | 45 | steps += 1 46 | if new_east == east_cucumbers and new_south == south_cucumbers: 47 | break 48 | 49 | east_cucumbers = new_east 50 | south_cucumbers = new_south 51 | 52 | # Answer One 53 | print(f'First step on which no sea cucumbers move: {steps}') 54 | -------------------------------------------------------------------------------- /2019/day_02.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 02 - 1202 Program Alarm.""" 2 | 3 | 4 | def run_intcode(memory, noun, verb): 5 | """Assign noun and verb then run intcode program on memory.""" 6 | memory[1] = noun 7 | memory[2] = verb 8 | pointer = 0 9 | while True: 10 | opcode = memory[pointer] 11 | if opcode == 99: 12 | return memory[0] 13 | 14 | param_one = memory[pointer + 1] 15 | param_two = memory[pointer + 2] 16 | param_three = memory[pointer + 3] 17 | 18 | if opcode == 1: 19 | memory[param_three] = memory[param_one] + memory[param_two] 20 | 21 | elif opcode == 2: 22 | memory[param_three] = memory[param_one] * memory[param_two] 23 | 24 | pointer += 4 25 | 26 | 27 | def find_gravity_assist_inputs(memory, desired_output): 28 | """Find noun and verb that gives desired_output when intcode is run.""" 29 | for noun in range(0, 100): 30 | for verb in range(0, 100): 31 | output = run_intcode(memory[:], noun, verb) 32 | if output == desired_output: 33 | return 100 * noun + verb 34 | return False 35 | 36 | 37 | with open('inputs/day_02.txt', 'r') as f: 38 | integers = [int(opcode) for opcode in f.read().split(',')] 39 | 40 | # Answer One 41 | print("Output prior to fire: {}".format(run_intcode(integers[:], 12, 2))) 42 | 43 | # Answer Two 44 | print("Inputs required for gravity assist calculation: {}".format( 45 | find_gravity_assist_inputs(integers, 19690720) 46 | )) 47 | 48 | -------------------------------------------------------------------------------- /2017/day_11.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 11 - Hex Ed""" 2 | 3 | 4 | def path(steps_string): 5 | """Find how far from the centre of a hex grid a list of steps takes you.""" 6 | # Strip and split file into a iterable list 7 | steps_list = steps_string.strip().split(',') 8 | 9 | # Break hexagon neighbours into cube coordinates 10 | x = 0 11 | y = 0 12 | z = 0 13 | furthest = 0 14 | for step in steps_list: 15 | # Match step with movement in cube coordinate terms 16 | if step == 'n': 17 | x += 1 18 | y -= 1 19 | 20 | elif step == 's': 21 | y += 1 22 | x -= 1 23 | 24 | elif step == 'ne': 25 | z += 1 26 | y -= 1 27 | 28 | elif step == 'sw': 29 | y += 1 30 | z -= 1 31 | 32 | elif step == 'nw': 33 | x += 1 34 | z -= 1 35 | 36 | elif step == 'se': 37 | z += 1 38 | x -= 1 39 | 40 | # Keep running track of largest value (furthest distance from centre) 41 | if max(x, y, z) > furthest: 42 | furthest = max(x, y, z) 43 | 44 | # Find biggest cube coordinate (shortest path) 45 | shortest_path = max(x, y, z) 46 | 47 | # Answer One 48 | print('The shortest path is', shortest_path, 'step long.') 49 | 50 | # Answer Two 51 | print('The furthest from the centre reached was', furthest, 'steps away.') 52 | 53 | 54 | with open('inputs/day_11.txt') as f: 55 | steps_string = f.read() 56 | path(steps_string) 57 | -------------------------------------------------------------------------------- /2016/day_22.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 22 - Grid Computing""" 2 | 3 | import re 4 | 5 | 6 | with open('inputs/day_22.txt') as f: 7 | data = [line.strip() for line in f.readlines()[2:]] 8 | 9 | viable = 0 10 | for node in data: 11 | used = int(re.search(r'T\s+(\d+)T', node).group(1)) 12 | if used == 0: 13 | continue 14 | 15 | for comp_node in data: 16 | avail = int(re.search(r'(\d+)T\s+\d+%', comp_node).group(1)) 17 | if avail >= used and comp_node != node: 18 | viable += 1 19 | 20 | # Answer One 21 | print("Viable pairs:", viable) 22 | 23 | # Part Two - Forming the structure and completing by hand seemed the best way after 24 | # printing out the viable pairs and realising this was a large sliding tile problem 25 | 26 | structure = [] 27 | i = 0 28 | while i < 24: 29 | row = [] 30 | j = 0 31 | while j < len(data): 32 | node_regex = re.search(r'x(\d+)-y(\d+)\s+(\d+)T\s+(\d+)T', data[i + j]) 33 | if node_regex.group(1) == '0' and node_regex.group(2) == '0': 34 | marker = '!' 35 | elif node_regex.group(1) == '37' and node_regex.group(2) == '0': 36 | marker = 'G' 37 | elif len(node_regex.group(3)) == 3: 38 | marker = '#' 39 | elif node_regex.group(4) == '0': 40 | marker = '_' 41 | else: 42 | marker = '.' 43 | row.append(marker) 44 | j += 24 45 | 46 | structure.append(row) 47 | i += 1 48 | 49 | with open('output.txt', 'w') as f: 50 | 51 | for row in structure: 52 | f.write(''.join(row) + '\n') 53 | -------------------------------------------------------------------------------- /2020/day_21.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 21 - Allergen Assessment.""" 4 | 5 | 6 | with open('inputs/day_21.txt') as f: 7 | rows = [line.strip() for line in f.readlines()] 8 | 9 | allergens = {} 10 | ingredients_list = [] 11 | for row in rows: 12 | ingredients, contains = row.split('(') 13 | ingredients = ingredients.split() 14 | ingredients_list.extend(ingredients) 15 | contains = [ 16 | x.strip(',') for x in contains[:-1].split() if x != 'contains' 17 | ] 18 | for contained in contains: 19 | if contained not in allergens: 20 | allergens[contained] = set(ingredients) 21 | else: 22 | allergens[contained] = allergens[contained] & set(ingredients) 23 | 24 | num_allergens = len(allergens) 25 | matched = set() 26 | while True: 27 | for allergen, ingredients in allergens.items(): 28 | if len(ingredients) == 1: 29 | matched.update(ingredients) 30 | continue 31 | 32 | allergens[allergen] = allergens[allergen] - matched 33 | 34 | if len(matched) == num_allergens: 35 | break 36 | 37 | non_allergen = 0 38 | for ingredient in ingredients_list: 39 | if ingredient not in matched: 40 | non_allergen += 1 41 | 42 | # Answer One 43 | print("Number of times non-allergen ingredients appear:", non_allergen) 44 | 45 | dangerous_list = '' 46 | for allergen in sorted(allergens): 47 | dangerous_list += allergens[allergen].pop() + ',' 48 | 49 | # Answer Two 50 | print("Canonical dangerous ingredients list:", dangerous_list.strip(',')) 51 | -------------------------------------------------------------------------------- /2022/day_25/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | file, _ := os.ReadFile("input.txt") 12 | numbers := strings.Split(strings.TrimSpace(string(file)), "\n") 13 | symbolToNum := map[string]int{ 14 | "=": -2, 15 | "-": -1, 16 | "0": 0, 17 | "1": 1, 18 | "2": 2, 19 | } 20 | 21 | decimalSum := 0 22 | for _, number := range numbers { 23 | value := 0 24 | exponent := 0 25 | for idx := len(number) - 1; idx >= 0; idx-- { 26 | char := string(number[idx]) 27 | value += symbolToNum[char] * (int(math.Pow(5, float64(exponent)))) 28 | exponent++ 29 | } 30 | decimalSum += value 31 | } 32 | 33 | exponent := 0 34 | for { 35 | if decimalSum < int(math.Pow(5, float64(exponent)))*2 { 36 | break 37 | } 38 | exponent++ 39 | } 40 | 41 | snafu := "" 42 | sum := 0 43 | for exponent >= 0 { 44 | expValue := int(math.Pow(5, float64(exponent))) 45 | potentialValues := map[string]int{ 46 | "=": sum + (expValue * -2), 47 | "-": sum + (-expValue), 48 | "0": sum, 49 | "1": sum + expValue, 50 | "2": sum + (expValue * 2), 51 | } 52 | 53 | closestnewValue := math.MaxInt 54 | closestSymbol := "" 55 | for char, newValue := range potentialValues { 56 | if math.Abs(float64(decimalSum)-float64(newValue)) < math.Abs(float64(decimalSum)-float64(closestnewValue)) { 57 | closestnewValue = newValue 58 | closestSymbol = char 59 | } 60 | } 61 | snafu += closestSymbol 62 | sum = closestnewValue 63 | 64 | exponent-- 65 | } 66 | 67 | fmt.Println("Answer One:", snafu) 68 | } 69 | -------------------------------------------------------------------------------- /2016/day_08.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 8 - Two-Factor Authentication""" 2 | 3 | import re 4 | 5 | 6 | with open('inputs/day_08.txt') as f: 7 | sequence = [line.strip() for line in f.readlines()] 8 | 9 | grid = [] 10 | for i in range(6): 11 | row = [] 12 | for j in range(50): 13 | row.append('.') 14 | grid.append(row) 15 | 16 | for instruction in sequence: 17 | if 'rect' in instruction: 18 | size = re.search(r'(\d+)x(\d+)', instruction) 19 | width, height = int(size.group(1)), int(size.group(2)) 20 | for i in range(height): 21 | for j in range(width): 22 | grid[i][j] = '#' 23 | 24 | elif 'row' in instruction: 25 | parse = re.search(r'y=(\d+) by (\d+)', instruction) 26 | row, pixels = int(parse.group(1)), int(parse.group(2)) 27 | for i in range(pixels): 28 | grid[row].insert(0, grid[row].pop(-1)) 29 | 30 | 31 | elif 'column' in instruction: 32 | parse = re.search(r'x=(\d+) by (\d+)', instruction) 33 | col, pixels = int(parse.group(1)), int(parse.group(2)) 34 | 35 | state_list = [] 36 | for row in grid: 37 | state_list.append(row[col]) 38 | for i in range(pixels): 39 | state_list.insert(0, state_list.pop(-1)) 40 | for i in range(len(grid)): 41 | grid[i][col] = state_list[i] 42 | 43 | on = 0 44 | for row in grid: 45 | for col in row: 46 | if col == '#': 47 | on += 1 48 | 49 | # Answer One 50 | print("Number of Lights On:", on) 51 | 52 | # Answer Two 53 | for row in grid: 54 | print(row) 55 | -------------------------------------------------------------------------------- /2017/day_19.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 19 - A Series of Tubes""" 2 | 3 | 4 | def traverse_maze(network, part_two=False): 5 | """Traverse the maze collecting letters and counting the steps taken.""" 6 | x = network[0].index('|') 7 | y = 0 8 | direction = 'D' 9 | letters = [] 10 | steps = 0 11 | while network[y][x] != ' ': 12 | if direction == 'D': 13 | y += 1 14 | elif direction == 'U': 15 | y -= 1 16 | elif direction == 'R': 17 | x += 1 18 | elif direction == 'L': 19 | x -= 1 20 | 21 | if network[y][x] == '+': 22 | if direction in ('L', 'R'): 23 | if y != len(network) - 1 and network[y+1][x] != ' ': 24 | direction = 'D' 25 | else: 26 | direction = 'U' 27 | else: 28 | if x != len(network[0]) - 1 and network[y][x+1] != ' ': 29 | direction = 'R' 30 | else: 31 | direction = 'L' 32 | 33 | elif network[y][x].isalpha(): 34 | letters.append(network[y][x]) 35 | 36 | steps += 1 37 | 38 | if not part_two: 39 | return ''.join(letters) 40 | 41 | return steps 42 | 43 | 44 | if __name__ == '__main__': 45 | 46 | with open('inputs/day_19.txt') as f: 47 | network = [line.strip('\n') for line in f.readlines()] 48 | 49 | # Answer One 50 | print("Letters along the path:", traverse_maze(network)) 51 | 52 | # Answer Two 53 | print("Length of the path:", traverse_maze(network, part_two=True)) 54 | -------------------------------------------------------------------------------- /2018/day_02.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 2 - Inventory Management System""" 2 | 3 | 4 | def repetition_checksum(ids): 5 | """Return checksum - No. of ids with doubled letters * No. with tripled.""" 6 | doubles, triples = 0, 0 7 | for code in ids: 8 | no_doubles, no_triples = True, True 9 | for letter in set(code): 10 | count = code.count(letter) 11 | if count == 2 and no_doubles: 12 | doubles += 1 13 | no_doubles = False 14 | elif count == 3 and no_triples: 15 | triples += 1 16 | no_triples = False 17 | 18 | if not no_doubles and not no_triples: 19 | break 20 | 21 | return doubles * triples 22 | 23 | 24 | def one_off(ids): 25 | """Find id pair that differs by one letter and return common letters.""" 26 | for index, code in enumerate(ids[:-1]): 27 | for comparison in ids[index+1:]: 28 | diff = [] 29 | to_compare = zip(code, comparison) 30 | for pos, pair in enumerate(to_compare): 31 | if pair[0] != pair[1]: 32 | diff.append(pos) 33 | if len(diff) > 1: 34 | break 35 | 36 | if len(diff) == 1: 37 | return code[:diff[0]] + code[diff[0]+1:] 38 | 39 | 40 | if __name__ == '__main__': 41 | 42 | with open('inputs/day_02.txt') as f: 43 | ids = f.readlines() 44 | 45 | # Answer One 46 | print("Checksum:", repetition_checksum(ids)) 47 | 48 | # Answer Two 49 | print("Letters in common:", one_off(ids)) 50 | -------------------------------------------------------------------------------- /2015/day_19.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 19 - Medicine for Rudolph""" 2 | 3 | import re 4 | 5 | 6 | with open('inputs/day_19.txt') as f: 7 | calibration = [line.strip() for line in f] 8 | 9 | mol = calibration[-1] 10 | replacements = calibration[:-2] 11 | 12 | new_list = set() 13 | for replacement in replacements: 14 | info = replacement.split(' ') 15 | change_from = info[0] 16 | change_to = info[2] 17 | 18 | i = 0 19 | while i < len(mol): 20 | if mol[i: i + len(change_from)] == change_from: 21 | new_mol = mol[:i] + change_to + mol[i + len(change_from):] 22 | i += len(change_from) 23 | new_list.add(new_mol) 24 | 25 | else: 26 | i += 1 27 | 28 | # Answer Part One 29 | print("Number of Distinct Molecules After One Replacement = " + str(len(new_list))) 30 | 31 | # Part Two 32 | 33 | # Function so it can be fed to re.sub 34 | def replace(rev_chem): 35 | """Return the appropriate substitution of an element string.""" 36 | return unsynth[rev_chem.group()] 37 | 38 | 39 | # Reverse to capture Ar subs before other part of the sub are carried out 40 | rev_mol = mol[::-1] 41 | 42 | rep_regex = re.compile(r'(\w+) => (\w+)') 43 | reps = rep_regex.findall('\n'.join(replacements)) 44 | 45 | # Build dict of reversed elements - k = replacement and v = replaced 46 | unsynth = {} 47 | for rep in reps: 48 | unsynth[rep[1][::-1]] = rep[0][::-1] 49 | 50 | count = 0 51 | while rev_mol != 'e': 52 | rev_mol = re.sub('|'.join(unsynth.keys()), replace, rev_mol, 1) 53 | count += 1 54 | 55 | print("Number of Steps to Synthesise Rudolph Cure =", count) 56 | -------------------------------------------------------------------------------- /2021/day_17.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 17 - Trick Shot""" 4 | 5 | import re 6 | 7 | 8 | def take_shot(x_vel, y_vel, x_max, y_min, target_area_coords): 9 | probe_x, probe_y = (0, 0) 10 | while True: 11 | probe_x += x_vel 12 | probe_y += y_vel 13 | if (probe_x, probe_y) in target_area_coords: 14 | return True 15 | 16 | if x_vel != 0: 17 | x_vel -= 1 if x_vel > 0 else -1 18 | 19 | y_vel -= 1 20 | 21 | if probe_y < y_min or probe_x > x_max: 22 | return False 23 | 24 | 25 | with open('inputs/day_17.txt', 'r') as aoc_input: 26 | target_area_regex = r'x=(-?\d+)\.\.(-?\d+), y=(-?\d+)\.\.(-?\d+)' 27 | target_area = re.findall(target_area_regex, aoc_input.read()) 28 | 29 | x_min, x_max, y_min, y_max = (int(x) for x in target_area[0]) 30 | target_area_coords = set() 31 | y = y_min 32 | while y <= y_max: 33 | x = x_min 34 | while x <= x_max: 35 | target_area_coords.add((x, y)) 36 | x += 1 37 | y += 1 38 | 39 | hits = [] 40 | x_vel = 0 41 | while x_vel <= x_max: 42 | 43 | y_vel = abs(y_min) 44 | while y_vel >= y_min: 45 | shot = take_shot(x_vel, y_vel, x_max, y_min, target_area_coords) 46 | if shot: 47 | hits.append((x_vel, y_vel)) 48 | 49 | y_vel -= 1 50 | 51 | x_vel += 1 52 | 53 | hits.sort(key=lambda x: x[1]) 54 | max_height = sum(range(1, hits[-1][1] + 1)) 55 | 56 | # Answer One 57 | print("Highest y position reached:", max_height) 58 | 59 | # Answer Two 60 | print("Number of initial velocity values that hit target area:", len(hits)) 61 | -------------------------------------------------------------------------------- /2019/day_16.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 16 - Flawed Frequency Transmission.""" 2 | 3 | 4 | def fft_phase(numbers, offset=0): 5 | """Perform a phase of flawed frequency transmission.""" 6 | output = [0 for __ in numbers] 7 | 8 | if offset > len(numbers) // 2: 9 | num_sum = sum(numbers[offset:]) 10 | for n in range(offset, len(numbers)): 11 | output[n] = num_sum % 10 12 | num_sum -= numbers[n] 13 | else: 14 | for i, __ in enumerate(numbers, offset): 15 | repeat = i + 1 16 | pattern_value = 1 17 | for n in range(repeat - 1, len(numbers), repeat * 2): 18 | output[i] += sum(numbers[n: n + repeat]) * pattern_value 19 | if pattern_value == 1: 20 | pattern_value = -1 21 | else: 22 | pattern_value = 1 23 | 24 | output[i] = abs(output[i]) % 10 25 | 26 | return output 27 | 28 | 29 | with open('inputs/day_16.txt') as f: 30 | input_list = [int(x) for x in list(f.read().strip())] 31 | 32 | num_list = input_list.copy() 33 | for _ in range(100): 34 | num_list = fft_phase(num_list) 35 | 36 | # Answer One 37 | last_eight = ''.join([str(n) for n in num_list[:8]]) 38 | print("Last eight digits after 100 rounds:", last_eight) 39 | 40 | offset = int("".join([str(n) for n in input_list[:7]])) 41 | 42 | num_list = input_list.copy() * 10000 43 | for __ in range(100): 44 | num_list = fft_phase(num_list, offset) 45 | 46 | # Answer Two 47 | message = ''.join([str(n) for n in num_list[offset: offset + 8]]) 48 | print("Eight digit embedded message:", message) 49 | -------------------------------------------------------------------------------- /2016/day_02.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 2 - Bathroom Security""" 2 | 3 | 4 | def get_code(start_pos, keypad, valid_pos): 5 | """Returns the code generated from instructions on specified keypad.""" 6 | pos = start_pos 7 | code = '' 8 | for line in lines: 9 | for move in line: 10 | if move == 'R': 11 | next_pos = [pos[0], pos[1] + 1] 12 | elif move == 'L': 13 | next_pos = [pos[0], pos[1] - 1] 14 | elif move == 'D': 15 | next_pos = [pos[0] + 1, pos[1]] 16 | elif move == 'U': 17 | next_pos = [pos[0] - 1, pos[1]] 18 | 19 | if next_pos in valid_pos: 20 | pos = next_pos 21 | 22 | code += keypad[pos[0]][pos[1]] 23 | 24 | return code 25 | 26 | 27 | basic = [ 28 | ['1', '2', '3'], 29 | ['4', '5', '6'], 30 | ['7', '8', '9'], 31 | ] 32 | 33 | advanced = [ 34 | ['', '', '1', '', ''], 35 | ['', '2', '3', '4', ''], 36 | ['5', '6', '7', '8', '9'], 37 | ['', 'A', 'B', 'C', ''], 38 | ['', '', 'D', '', ''], 39 | ] 40 | 41 | with open('inputs/day_02.txt') as f: 42 | lines = [line.strip() for line in f.readlines()] 43 | 44 | # Answer Part One 45 | buttons = [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] 46 | print("Bathroom Code =", get_code([1, 1], basic, buttons)) 47 | 48 | # Answer Part Two 49 | adv_but = [[0, 2], [1, 1], [1, 2], [1, 3], [2, 0], [2, 1], [2, 2], [2, 3], 50 | [2, 4], [3, 1], [3, 2], [3, 3],[4, 2]] 51 | print("Advanced Bathroom Code =", get_code([1, 1], advanced, adv_but)) 52 | -------------------------------------------------------------------------------- /2017/day_10.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 10 - Knot Hash""" 2 | 3 | 4 | def hashed(lengths, rounds): 5 | """Carry out a simulation of a knot tying hash given length inputs. """ 6 | notches = list(range(256)) 7 | skip = 0 8 | pos = 0 9 | for __ in range(rounds): 10 | for length in lengths: 11 | to_rev = [] 12 | for i in range(length): 13 | to_rev.append(notches[(pos + i) % 256]) 14 | 15 | for j in range(length): 16 | notches[(pos + j) % 256] = to_rev.pop() 17 | pos += skip + length 18 | skip += 1 19 | 20 | return notches 21 | 22 | 23 | def dense(sparse): 24 | """Turn a sparse hash into a dense hash by XORing 16 digit blocks.""" 25 | dense = [] 26 | position = 0 27 | while position + 16 <= len(sparse): 28 | total = sparse[position] 29 | for pos in range(position + 1, position + 16): 30 | total = total ^ sparse[pos] 31 | dense.append(total) 32 | position += 16 33 | 34 | return dense 35 | 36 | 37 | if __name__ == '__main__': 38 | 39 | puzzle_input = '230,1,2,221,97,252,168,169,57,99,0,254,181,255,235,167' 40 | puzzle_list = [int(x) for x in puzzle_input.split(',')] 41 | ascii_input = [ord(c) for c in ''.join(puzzle_input)] 42 | [ascii_input.append(length) for length in (17, 31, 73, 47, 23)] 43 | 44 | # Answer One 45 | notches = hashed(puzzle_list, 1) 46 | print("Product of first two numbers in hash:", notches[0] * notches[1]) 47 | 48 | # Answer Two 49 | hexed = "".join([format(num, '02x') for num in dense(hashed(ascii_input, 64))]) 50 | print("Knot hash:", hexed) 51 | -------------------------------------------------------------------------------- /2017/day_17.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 17 - Spinlock""" 2 | 3 | def spinlock(): 4 | """Find the value adjacent to a spinlocks final insertion.""" 5 | steps = 304 6 | position = 0 7 | to_insert = 1 8 | buffer = [0] 9 | 10 | insertions = 2017 11 | 12 | while insertions > 0: 13 | 14 | position = (position + steps) % len(buffer) 15 | 16 | temp_buffer = [] 17 | for value in buffer[:position + 1]: 18 | temp_buffer.append(value) 19 | 20 | temp_buffer.append(to_insert) 21 | to_insert += 1 22 | 23 | for value in buffer[position + 1:]: 24 | temp_buffer.append(value) 25 | 26 | position += 1 27 | buffer = temp_buffer 28 | insertions -= 1 29 | 30 | if position == len(buffer) - 1: 31 | adjacent = 0 32 | 33 | else: 34 | adjacent = position + 1 35 | 36 | return 'Value after 2017 insertions: ' + str(buffer[adjacent]) 37 | 38 | 39 | def spinlock_sequel(): 40 | """Find the value adjacent to 0 after 50000000 insertions.""" 41 | steps = 304 42 | position = 0 43 | to_insert = 1 44 | buffer_length = 1 45 | 46 | insertions = 50000000 47 | 48 | while insertions > 0: 49 | 50 | position = (position + steps) % buffer_length 51 | 52 | if position == 0: 53 | adjacent = to_insert 54 | 55 | position += 1 56 | buffer_length += 1 57 | to_insert += 1 58 | insertions -= 1 59 | 60 | return adjacent 61 | 62 | 63 | # Answer One 64 | print("Value after 2017:", spinlock()) 65 | 66 | # Answer Two 67 | print("Value after 0 once 50000000 is inserted:", spinlock_sequel()) 68 | -------------------------------------------------------------------------------- /2017/day_05.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 5 - A Maze of Twisty Trampolines, All Alike""" 2 | 3 | 4 | def steps_taken(offsets): 5 | """Calculate the steps needed to traverse an offset maze.""" 6 | # Put the offsets in a list 7 | value_list = offsets.split('\n') 8 | 9 | # Starting at [0] follow the offsets, adding one to each when used 10 | list_position = 0 11 | steps = 0 12 | 13 | while list_position < len(value_list): 14 | offset = int(value_list[list_position]) 15 | value_list[list_position] = int(value_list[list_position]) + 1 16 | steps += 1 17 | list_position += offset 18 | 19 | return steps 20 | 21 | 22 | def jumps_taken(offsets): 23 | """Calculate the steps needed to traverse a modified offset maze.""" 24 | # Put the offsets in a list 25 | value_list = offsets.split('\n') 26 | 27 | # Follow offsets, adding one if < 3 and taking 1 if not until maze end 28 | list_position = 0 29 | steps = 0 30 | 31 | while list_position < len(value_list): 32 | offset = int(value_list[list_position]) 33 | # Change offset ot the appropriate new value 34 | if offset < 3: 35 | value_list[list_position] = int(value_list[list_position]) + 1 36 | else: 37 | value_list[list_position] = int(value_list[list_position]) - 1 38 | 39 | steps += 1 40 | list_position += offset 41 | 42 | return steps 43 | 44 | 45 | with open('inputs/day_05.txt') as f: 46 | offsets = f.read().strip() 47 | 48 | 49 | # Answer One 50 | print("Number of steps to reach the exit:", steps_taken(offsets)) 51 | 52 | # Answer Two 53 | print("Number of steps to reach the exit:", jumps_taken(offsets)) 54 | -------------------------------------------------------------------------------- /2020/day_03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 03 - Toboggan Trajectory.""" 4 | 5 | 6 | def count_trees_on_slope(area: list, right_step: int, down_step: int) -> int: 7 | """Return number of trees encounterd in area on slope (right/down step). 8 | 9 | Args: 10 | area: Rows of characters representing coordinates of a map. 11 | right_step: Amount to move to the right at each section of the slope. 12 | down_step: Amount to move down at each section of the slope. 13 | 14 | Returns: 15 | The number of trees ("#" characters) encountered before reaching past 16 | the last row of the area. 17 | 18 | """ 19 | # Determine boundaries of the area 20 | index_of_last_row = len(area) - 1 21 | index_of_last_col = len(area[0]) - 1 22 | 23 | y = x = 0 24 | trees = 0 25 | while y <= index_of_last_row: 26 | if rows[y][x] == "#": 27 | trees += 1 28 | 29 | x = (x + right_step) % index_of_last_col 30 | y += down_step 31 | 32 | return trees 33 | 34 | 35 | with open ('inputs/day_03.txt', 'r') as password_database: 36 | rows = [line for line in password_database.readlines()] 37 | 38 | trees = count_trees_on_slope(rows, 3, 1) 39 | 40 | # Answer One 41 | print("Number of trees encountered on right 3 down 1 slope:", trees) 42 | 43 | product_of_trees = trees 44 | product_of_trees *= count_trees_on_slope(rows, 1, 1) 45 | product_of_trees *= count_trees_on_slope(rows, 5, 1) 46 | product_of_trees *= count_trees_on_slope(rows, 7, 1) 47 | product_of_trees *= count_trees_on_slope(rows, 1, 2) 48 | 49 | # Answer Two 50 | print("Product of trees encountered on 5 different slopes:", product_of_trees) 51 | -------------------------------------------------------------------------------- /2022/day_10/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type Cycle struct { 12 | strength int 13 | x int 14 | } 15 | 16 | func main() { 17 | file, _ := os.Open("input.txt") 18 | scanner := bufio.NewScanner(file) 19 | instructions := []string{} 20 | for scanner.Scan() { 21 | instructions = append(instructions, scanner.Text()) 22 | } 23 | 24 | x := 1 25 | currentCycle := 1 26 | cycles := map[int]Cycle{} 27 | for _, instruction := range instructions { 28 | cycles[currentCycle] = Cycle{strength: x * currentCycle, x: x} 29 | if instruction == "noop" { 30 | currentCycle++ 31 | } else { 32 | incXBy, _ := strconv.Atoi(instruction[5:]) 33 | currentCycle++ 34 | cycles[currentCycle] = Cycle{strength: x * currentCycle, x: x} 35 | currentCycle++ 36 | cycles[currentCycle] = Cycle{strength: x * currentCycle, x: x} 37 | x += incXBy 38 | } 39 | } 40 | 41 | strengthSum := 0 42 | for i := 20; ; i += 40 { 43 | if i > 220 { 44 | break 45 | } 46 | 47 | strengthSum += cycles[i].strength 48 | } 49 | 50 | fmt.Println("Answer One:", strengthSum) 51 | 52 | crt := []string{} 53 | row := []string{} 54 | for i := 1; i <= currentCycle; i++ { 55 | cycle := cycles[i] 56 | x := cycle.x 57 | pixel := i - (40 * len(crt)) - 1 58 | 59 | if pixel == x-1 || pixel == x || pixel == x+1 { 60 | row = append(row, "##") 61 | } else { 62 | row = append(row, " ") 63 | } 64 | 65 | if (i % 40) == 0 { 66 | crt = append(crt, strings.Join(row, "")) 67 | row = []string{} 68 | } 69 | } 70 | 71 | fmt.Print("\nAnswer Two:\n\n") 72 | for _, row := range crt { 73 | fmt.Println(row) 74 | } 75 | fmt.Print("\n") 76 | } 77 | -------------------------------------------------------------------------------- /2017/day_24.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 24 - Electromagnetic Moat""" 2 | 3 | 4 | def extend(join, unused, strength, length): 5 | """Try to extend a bridge by adding an unused, matching part to it.""" 6 | info.append((strength, length)) 7 | for to_try in unused: 8 | if join in to_try: 9 | updated_unused = [part for part in unused if part != to_try] 10 | left, right = to_try 11 | if left == join: 12 | new_join = right 13 | else: 14 | new_join = left 15 | 16 | extend(new_join, updated_unused, strength + sum(to_try), length + 1) 17 | 18 | 19 | def start_bridges(part_two=False): 20 | """Find all parts with a 0 connection to use as the start of a bridge.""" 21 | for component in parts: 22 | if 0 in component: 23 | left, right = component 24 | unused = [part for part in parts if part != component] 25 | if left == 0: 26 | join = right 27 | else: 28 | join = left 29 | 30 | extend(join, unused, sum(component), 1) 31 | 32 | if not part_two: 33 | return max([strength[0] for strength in info]) 34 | 35 | longest = max([length[1] for length in info]) 36 | return max([strength[0] for strength in info if strength[1] == longest]) 37 | 38 | 39 | if __name__ == '__main__': 40 | 41 | with open('inputs/day_24.txt') as f: 42 | parts = [tuple(map(int, line.split("/"))) for line in f] 43 | 44 | info = [] # Keep track of bridge strengths and lengths 45 | 46 | # Answer One 47 | print("Strength of the strongest bridge:", start_bridges()) 48 | 49 | # Answer Two 50 | print("Strength of the strongest of the longest:", start_bridges(part_two=True)) 51 | -------------------------------------------------------------------------------- /2019/day_08.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 8 - Space Image Format.""" 2 | 3 | 4 | def check_corruption(layers): 5 | """Return one count * two count of layer with the least zeros in it.""" 6 | least_zeros = (None, None) 7 | for layer, values in layers.items(): 8 | zeros = values.count(0) 9 | if not least_zeros[0] or zeros < least_zeros[0]: 10 | least_zeros = (zeros, layer) 11 | 12 | zeros_layer = layers[least_zeros[1]] 13 | return zeros_layer.count(1) * zeros_layer.count(2) 14 | 15 | 16 | def decode_image(layers, height, width): 17 | """Combine the layers and return the decoded image.""" 18 | final_image = [] 19 | for row in range(height): 20 | current_row = [] 21 | for col in range(width): 22 | pixel_pos = col + row * width 23 | for layer in range(len(layers)): 24 | pixel = layers[layer][pixel_pos] 25 | if pixel == 0: 26 | current_row.append('#') 27 | break 28 | elif pixel == 1: 29 | current_row.append(' ') 30 | break 31 | final_image.append(current_row) 32 | 33 | return final_image 34 | 35 | 36 | with open('inputs/day_08.txt', 'r') as f: 37 | digits = [int(x) for x in list(f.read().strip())] 38 | 39 | height, width = (6, 25) 40 | size = height * width 41 | 42 | i = 0 43 | layers = {} 44 | layer = 0 45 | while i < len(digits): 46 | layers[layer] = digits[i: i+size] 47 | i += size 48 | layer += 1 49 | 50 | # Answer One 51 | print("Corruption Check", check_corruption(layers)) 52 | 53 | # Answer Two 54 | image = decode_image(layers, height, width) 55 | print("Decoded Image:", end='\n\n') 56 | for row in image: 57 | print(''.join(row)) 58 | -------------------------------------------------------------------------------- /2021/day_11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 11 - Dumbo Octopus""" 4 | 5 | 6 | with open('inputs/day_11.txt', 'r') as aoc_input: 7 | lines = [line.strip() for line in aoc_input.readlines()] 8 | 9 | octopi = {} 10 | for y, row in enumerate(lines): 11 | for x, level in enumerate(row): 12 | octopi[(x, y)] = int(level) 13 | 14 | num_flashes = 0 15 | step = 1 16 | synchronised = False 17 | while not synchronised: 18 | 19 | flashed = set() 20 | for coords, level in octopi.items(): 21 | octopi[coords] = level + 1 22 | 23 | for coords, level in octopi.items(): 24 | if level > 9 and coords not in flashed: 25 | flashed.add(coords) 26 | 27 | to_flash = [coords] 28 | while to_flash: 29 | x, y = to_flash.pop() 30 | adjacent = [ 31 | (x + 1, y), (x - 1, y), (x + 1, y + 1), (x - 1, y - 1), 32 | (x, y + 1), (x, y - 1), (x + 1, y - 1), (x - 1, y + 1) 33 | ] 34 | 35 | for next_to in adjacent: 36 | if octopi.get(next_to, None): 37 | octopi[next_to] += 1 38 | 39 | if octopi[next_to] > 9 and next_to not in flashed: 40 | flashed.add(next_to) 41 | to_flash.append(next_to) 42 | 43 | 44 | for coords in flashed: 45 | octopi[coords] = 0 46 | num_flashes += 1 47 | 48 | if step == 100: 49 | # Answer One 50 | print("Total flashes after 100 steps:", num_flashes) 51 | 52 | if len(set(octopi.values())) == 1: 53 | break 54 | 55 | step += 1 56 | 57 | # Answer Two 58 | print("First step where all octopi are synchronised:", step) 59 | -------------------------------------------------------------------------------- /2021/day_09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 9 - Smoke Basin""" 4 | 5 | 6 | with open('inputs/day_09.txt', 'r') as aoc_input: 7 | rows = [line.strip() for line in aoc_input.readlines()] 8 | 9 | height_map = {} 10 | for y, row in enumerate(rows): 11 | for x, height in enumerate(row): 12 | height_map[(x, y)] = int(height) 13 | 14 | low_points = [] 15 | low_points_sum = 0 16 | for coords, height in height_map.items(): 17 | x, y = coords 18 | adjacent = ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)) 19 | lowest = True 20 | for neighbour in adjacent: 21 | if height_map.get(neighbour, 10) <= height: 22 | lowest = False 23 | break 24 | 25 | if lowest: 26 | low_points.append(coords) 27 | low_points_sum += height + 1 28 | 29 | # Answer One 30 | print("Sum of risk levels of low points:", low_points_sum) 31 | 32 | basin_sizes = [] 33 | for low_point in low_points: 34 | part_of_basin = set([low_point]) 35 | 36 | coords_to_check_stack = [low_point] 37 | while coords_to_check_stack: 38 | x, y = coords_to_check_stack.pop() 39 | adjacent = ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)) 40 | for neighbour in adjacent: 41 | neighbour_height = height_map.get(neighbour, 0) 42 | if neighbour_height > height_map[(x, y)] and neighbour_height != 9: 43 | part_of_basin.add(neighbour) 44 | coords_to_check_stack.append(neighbour) 45 | 46 | basin_sizes.append(len(part_of_basin)) 47 | 48 | basin_sizes.sort(reverse=True) 49 | 50 | largest_three_product = 1 51 | for size in basin_sizes[:3]: 52 | largest_three_product *= size 53 | 54 | # Answer Two 55 | print("Product of three largest basin sizes:", largest_three_product) 56 | -------------------------------------------------------------------------------- /2015/day_09.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 9 - All in a Single Night""" 2 | 3 | import itertools 4 | import re 5 | 6 | with open('inputs/day_09.txt') as f: 7 | routes = [route.strip() for route in f] 8 | 9 | # Parse routes and make list of places for permutation 10 | places = set() 11 | for route in routes: 12 | info = route.split(' ') 13 | start = info[0] 14 | destination = info[2] 15 | places.add(start) 16 | places.add(destination) 17 | 18 | # Generate journey permutations 19 | permutations = list(itertools.permutations(places)) 20 | 21 | # Go through permutations calculating distance 22 | shortest = 1000 23 | longest = 0 24 | for permutation in permutations: 25 | overall_distance = 0 26 | i = 0 27 | while i < len(permutation) - 1: 28 | start = permutation[i] 29 | destination = permutation[i + 1] 30 | 31 | # Copy routes list as a string for regex purposes 32 | regex_routes = ' '.join(routes) 33 | journey_regex = re.compile(r'{} to {} = (\d*)'.format(start, destination)) 34 | alt_regex = re.compile(r'{} to {} = (\d*)'.format(destination, start)) 35 | 36 | # Search for permutation from input.txt info in both possible formats 37 | if journey_regex.search(regex_routes): 38 | distance = journey_regex.search(regex_routes).group(1) 39 | else: 40 | distance = alt_regex.search(regex_routes).group(1) 41 | 42 | overall_distance += int(distance) 43 | 44 | i += 1 45 | 46 | if overall_distance < shortest: 47 | shortest = overall_distance 48 | 49 | if overall_distance > longest: 50 | longest = overall_distance 51 | 52 | print("Answer One: The Shortest Route Has a Distance of {}".format(shortest)) 53 | 54 | print("Answer Two: The Longest Route Has a Distance of {}".format(longest)) 55 | -------------------------------------------------------------------------------- /2017/day_02.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 2 - Corruption Checksum""" 2 | 3 | def difference_checksum(string): 4 | """Take number rows and return the sum of each rows high/low difference.""" 5 | total = 0 6 | # Split string so each row is its own embedded list 7 | row_list = [] 8 | rows_split = string.split('\n') 9 | for i in rows_split: 10 | row_string = str(i) 11 | row = row_string.split() 12 | row_list.append(row) 13 | 14 | # Sort each row into ascending order, calculate difference and add to total 15 | for row in row_list: 16 | sorted_list = sorted(row, key=int) 17 | total += int(sorted_list[-1]) - int(sorted_list[0]) 18 | 19 | return total 20 | 21 | 22 | def divisible_cheksum(string): 23 | """Take rows of numbers and return the sum of their divisible pairs.""" 24 | total = 0 25 | # Split string so each row is its own embedded list 26 | row_list = [] 27 | rows_split = string.split('\n') 28 | for i in rows_split: 29 | row_string = str(i) 30 | row = row_string.split() 31 | row_list.append(row) 32 | 33 | # For each number in row, see if it divides with no remainder 34 | for row in row_list: 35 | for number in row: 36 | count = 0 37 | while count < len(row) - 1: 38 | # Check division order and that it divides with no remainder 39 | if (int(number) > int(row[count + 1]) 40 | and int(number) % int(row[count + 1]) == 0): 41 | total += int(number) // int(row[count + 1]) 42 | count += 1 43 | 44 | return total 45 | 46 | 47 | with open('inputs/day_02.txt') as f: 48 | rows = f.read() 49 | 50 | # Answer One 51 | print(difference_checksum(rows)) 52 | 53 | # Answer Two 54 | print(divisible_cheksum(rows)) 55 | -------------------------------------------------------------------------------- /2018/day_08.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 8 - Memory Maneuver""" 2 | 3 | 4 | def process_node(node, data): 5 | """Determine and process the next node in data.""" 6 | nodes[node] = None 7 | child_nums = None 8 | children = data[0] 9 | num_meta = data[1] 10 | data = data[2:] 11 | 12 | if children: 13 | data, child_nums = process_children(children, data) 14 | 15 | nodes[node] = (data[:num_meta], child_nums) 16 | return data[num_meta:] 17 | 18 | 19 | def process_children(children, data): 20 | """Process next node in data until no children remain.""" 21 | child_nums = [] 22 | while children: 23 | node = len(nodes) 24 | child_nums.append(node) 25 | nodes[node] = None 26 | data = process_node(node, data) 27 | children -= 1 28 | 29 | return (data, child_nums) 30 | 31 | 32 | def node_value(node): 33 | """Recursively calculate a nodes value from its children's values.""" 34 | indexed, children = nodes[node] 35 | if not children: 36 | return sum(nodes[node][0]) 37 | 38 | value = 0 39 | for index in indexed: 40 | try: 41 | value += node_value(children[index-1]) 42 | except IndexError: 43 | continue 44 | 45 | return value 46 | 47 | 48 | if __name__ == '__main__': 49 | 50 | with open('inputs/day_08.txt') as f: 51 | data = [int(x) for x in f.read().split()] 52 | 53 | # Process nodes from root and map nodes to their metadata and children 54 | nodes = {} 55 | process_node(0, data) 56 | 57 | metadata_sum = 0 58 | for node, info in nodes.items(): 59 | for metadata in info[0]: 60 | metadata_sum += metadata 61 | 62 | # Answer One 63 | print("Sum of metadata:", metadata_sum) 64 | 65 | # Answer Two 66 | print("Root value:", node_value(0)) 67 | -------------------------------------------------------------------------------- /2018/day_11.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 11 - Chronal Charge""" 2 | 3 | 4 | def max_square(size, grid): 5 | """Find square (with size side length) with highest power total.""" 6 | grid_size = len(grid) 7 | max_power, max_coord = 0, None 8 | for row in range(1, grid_size - size + 1): 9 | for col in range(1, grid_size - size + 1): 10 | a = grid[row - 1][col - 1] 11 | b = grid[row - 1][col + size - 1] 12 | c = grid[row + size - 1][col - 1] 13 | d = grid[row + size - 1][col + size - 1] 14 | power_sum = a + d - b - c 15 | if power_sum > max_power: 16 | max_power = power_sum 17 | max_coord = (col, row) 18 | 19 | return (max_power, (max_coord)) 20 | 21 | 22 | serial = 7672 23 | end_cell = 301 24 | 25 | # Cell power grid - 0's on left/top makes conversion to summed-area easier 26 | grid = [[0 for __ in range(end_cell)] for __ in range(end_cell)] 27 | for row in range(1, end_cell): 28 | for col in range(1, end_cell): 29 | rack_id = col + 10 30 | power = (rack_id * row + serial) * rack_id 31 | power = int(list(str(power))[-3]) - 5 32 | grid[row][col] = power 33 | 34 | # Transform grid into summed_area grid 35 | for row in range(1, end_cell): 36 | for col in range(1, end_cell): 37 | grid[row][col] = (grid[row][col] + grid[row - 1][col] 38 | + grid[row][col - 1] - grid[row - 1][col - 1]) 39 | 40 | # Answer One 41 | print("Coord for 3x3 square with most power:", max_square(3, grid)[1]) 42 | 43 | max_power = 0 44 | for size in range(1, end_cell): 45 | power, coord = max_square(size, grid) 46 | if power > max_power: 47 | max_power, max_coord = power, coord 48 | max_size = size 49 | 50 | # Answer Two 51 | print("Coord and size with most power:", max_coord, max_size) 52 | -------------------------------------------------------------------------------- /2015/day_16.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 16 - Aunt Sue""" 2 | 3 | import re 4 | 5 | 6 | with open('inputs/day_16.txt') as f: 7 | sue_list = f.readlines() 8 | 9 | 10 | mfcsam_dict = {'children': 3, 'cats': 7, 'samoyeds': 2, 'pomeranians': 3, 11 | 'akitas': 0, 'vizslas': 0, 'goldfish': 5, 'trees': 3, 12 | 'cars': 2, 'perfumes': 1,} 13 | 14 | wrong_aunt = [] 15 | for num, aunt in enumerate(sue_list, 1): 16 | for key in mfcsam_dict: 17 | info_regex = re.compile(r'{}: (\d+)'.format(key)) 18 | info_point = info_regex.search(aunt) 19 | if info_point: 20 | if int(info_point.group(1)) != mfcsam_dict[key]: 21 | wrong_aunt.append(num) 22 | break 23 | 24 | for number in range(1, len(sue_list)): 25 | if number not in wrong_aunt: 26 | print("Aunt One =", number) 27 | break 28 | 29 | # Part Two 30 | 31 | wrong_aunt = [] 32 | for num, aunt in enumerate(sue_list, 1): 33 | for key in mfcsam_dict: 34 | info_regex = re.compile(r'{}: (\d+)'.format(key)) 35 | info_point = info_regex.search(aunt) 36 | if info_point and key not in ('cats', 'trees', 'pomeranians', 'goldfish'): 37 | if int(info_point.group(1)) != mfcsam_dict[key]: 38 | wrong_aunt.append(num) 39 | break 40 | elif info_point and key in ('cats', 'trees'): 41 | if int(info_point.group(1)) <= mfcsam_dict[key]: 42 | wrong_aunt.append(num) 43 | break 44 | 45 | elif info_point and key in ('pomeranians', 'goldfish'): 46 | if int(info_point.group(1)) >= mfcsam_dict[key]: 47 | wrong_aunt.append(num) 48 | break 49 | 50 | 51 | for number in range(1, len(sue_list)): 52 | if number not in wrong_aunt: 53 | print("Aunt Two =", number) 54 | break 55 | -------------------------------------------------------------------------------- /2017/day_03.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 3 - Spiral Memory""" 2 | 3 | 4 | def distance(target): 5 | """Find how far input is away from the centre of a number spiral.""" 6 | # Find which odd square layer contains the input 7 | layers = 0 8 | number = 1 9 | line_max = 0 10 | while line_max < target: 11 | number += 2 12 | line_max = number * number 13 | layers += 1 14 | 15 | # Calculate all four mid points of layer 16 | mid_values = [line_max - layers, line_max - layers * 3, 17 | line_max - layers * 5, line_max - layers * 7] 18 | 19 | # Find nearest mid point to target number and the distance to it 20 | mid_distance = number 21 | for value in mid_values: 22 | if abs(target - value) < mid_distance: 23 | mid_distance = abs(target - value) 24 | 25 | # Return value of distance to middle + layers 26 | return mid_distance + layers 27 | 28 | 29 | def next_coords(x, y): 30 | """Return the next coordinate pair of an anticlockwise spiral.""" 31 | if y == 0 and x == 0: 32 | coords = (1, 0) 33 | elif y > -x and x > y: 34 | coords = (x, y + 1) 35 | elif y > -x and y >= x: 36 | coords = (x - 1, y) 37 | elif y <= -x and x >= y: 38 | coords = (x + 1, y) 39 | elif y <= -x and x < y: 40 | coords = (x, y - 1) 41 | 42 | return coords 43 | 44 | 45 | puzzle_input = 361527 46 | # Initial Coordinates and Values Dictionary 47 | x, y = 0, 0 48 | values = { (0, 0): 1 } 49 | 50 | while values[(x, y)] <= puzzle_input: 51 | x, y = next_coords(x, y) 52 | offsets = [-1, 0, 1] 53 | values[(x, y)] = (sum(values.get((x + i, y + j), 0) 54 | for i in offsets for j in offsets)) 55 | 56 | # Answer One 57 | print("Steps required:", distance(361527)) 58 | 59 | # Answer Two 60 | print("Answer value: " + str(values[(x, y)])) 61 | -------------------------------------------------------------------------------- /2017/day_06.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 6 - Memory Reallocation""" 2 | 3 | 4 | def mem_stats(num_list, cycle_count=None): 5 | """Return how many redistribution cycles occur before an infinite loop.""" 6 | # Split into list of memory banks and save config for uniqueness check 7 | current = num_list.split() 8 | current = [int(x) for x in current] 9 | configs = [] 10 | configs.append(tuple(current)) 11 | 12 | finite = True 13 | while finite is True: 14 | # Find the bank with the most blocks in it 15 | highest = 0 16 | for bank in current: 17 | if bank > highest: 18 | highest = bank 19 | position = current.index(bank) 20 | 21 | # Redistribute the blocks to all memory banks 22 | current[position] = 0 23 | while highest > 0: 24 | if position < len(current) - 1: 25 | current[position + 1] = current[position + 1] + 1 26 | 27 | else: 28 | current[0] = int(current[0]) + 1 29 | position = -1 30 | 31 | highest -= 1 32 | position += 1 33 | 34 | # If current not in configs list return pertient value 35 | if tuple(current) in configs: 36 | finite = False 37 | if cycle_count is None: 38 | return 'Cycles until infinite loop: ' + str(len(configs)) 39 | else: 40 | return ('Loops between repetitions: ' 41 | + str(len(configs) - configs.index(tuple(current)))) 42 | 43 | else: 44 | configs.append(tuple(current)) 45 | 46 | 47 | PUZZLE_INPUT = '10 3 15 10 5 15 5 15 9 2 5 8 5 2 3 6' 48 | 49 | # Answer One 50 | print("Number of redistribution cycles:", mem_stats(PUZZLE_INPUT)) 51 | 52 | # Answer Two 53 | print("Number of loops between repetitions:", mem_stats(PUZZLE_INPUT, cycle_count='yes')) 54 | -------------------------------------------------------------------------------- /2020/day_10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 10 - Adapter Array.""" 4 | 5 | 6 | with open ('inputs/day_10.txt', 'r') as adapters: 7 | outputs = [int(number.strip()) for number in adapters.readlines()] 8 | 9 | outputs.append(max(outputs) + 3) 10 | outputs.append(0) 11 | available_adapters = set(outputs) 12 | 13 | joltage_dict = {} 14 | for output in outputs: 15 | joltage_dict[output] = set() 16 | for number in range(output - 3, output): 17 | if number in available_adapters: 18 | joltage_dict[output].add(number) 19 | 20 | used_adapters = set() 21 | while len(used_adapters) < len(available_adapters) - 1: 22 | for joltage, possible_adapters in joltage_dict.items(): 23 | if len(possible_adapters) == 1: 24 | used_adapters.update(possible_adapters) 25 | continue 26 | 27 | unused = set() 28 | for adapter in possible_adapters: 29 | if adapter not in used_adapters: 30 | unused.add(adapter) 31 | 32 | joltage_dict[joltage] = unused 33 | 34 | del joltage_dict[0] 35 | 36 | diff_of_one = 0 37 | diff_of_three = 0 38 | for adapter, connected_to in joltage_dict.items(): 39 | connected = connected_to.pop() 40 | if adapter - connected == 1: 41 | diff_of_one += 1 42 | elif adapter - connected == 3: 43 | diff_of_three += 1 44 | 45 | # Answer One 46 | print("1-jolt multiplied by 3-jolt differences:", diff_of_one * diff_of_three) 47 | 48 | del outputs[-1] # Remove 0 from output options 49 | adapter_paths = {} 50 | adapter_paths[0] = 1 51 | for adapter in sorted(outputs): 52 | paths_to = 0 53 | for n in (1, 2, 3): 54 | paths_to += adapter_paths.get(adapter - n, 0) 55 | adapter_paths[adapter] = paths_to 56 | 57 | # Answer Two 58 | print("Number of ways to arrange adapters to connect outlet to device:", 59 | adapter_paths[outputs[-1]]) 60 | -------------------------------------------------------------------------------- /2015/day_13.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 13 - Knights of the Dinner Table""" 2 | 3 | import itertools 4 | import re 5 | 6 | 7 | def calc_happiness(person, neighbour): 8 | """Returns the overall hapiness of a pair of people.""" 9 | if person == 'me' or neighbour == 'me': 10 | return 0 11 | 12 | pair_happiness = 0 13 | p_regex = re.compile(r'{} would (gain|lose) (\d+) .* {}'.format(person, neighbour)) 14 | n_regex = re.compile(r'{} would (gain|lose) (\d+) .* {}'.format(neighbour, person)) 15 | effect_1 = p_regex.search(data).group(1) 16 | amount_1 = p_regex.search(data).group(2) 17 | effect_2 = n_regex.search(data).group(1) 18 | amount_2 = n_regex.search(data).group(2) 19 | 20 | if effect_1 == 'gain': 21 | pair_happiness += int(amount_1) 22 | else: 23 | pair_happiness -= int(amount_1) 24 | 25 | if effect_2 == 'gain': 26 | pair_happiness += int(amount_2) 27 | else: 28 | pair_happiness -= int(amount_2) 29 | 30 | return pair_happiness 31 | 32 | 33 | with open('inputs/day_13.txt') as f: 34 | data = f.readlines() 35 | 36 | # Find all guests for iteration 37 | guests = [] 38 | for line in data: 39 | guest = line.split(' ')[0] 40 | if guest not in guests: 41 | guests.append(guest) 42 | # guests.append('me') # Uncomment this for Part Two 43 | 44 | perms = list(itertools.permutations(guests)) 45 | # Evaluate Hapiness for Each Perm 46 | data = ','.join(data) # Convert to string for regex reasons 47 | happiest = 0 48 | for perm in perms: 49 | happiness = 0 50 | 51 | i = 0 52 | while i < len(perm) - 1: 53 | person = perm[i] 54 | neighbour = perm[i + 1] 55 | happiness += calc_happiness(person, neighbour) 56 | i += 1 57 | 58 | happiness += calc_happiness(perm[0], perm[-1]) 59 | 60 | if happiness > happiest: 61 | happiest = happiness 62 | 63 | print(happiest) 64 | -------------------------------------------------------------------------------- /2016/day_07.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 7 - Internet Protocol Version 7""" 2 | 3 | import re 4 | 5 | 6 | def check_tls(address): 7 | """Check address has TLS support - ABBA only found in supernet.""" 8 | abba_regex = re.compile(r'(\w)(?!\1)(\w)\2\1') 9 | hypernet = re.findall(r'\[[^\]]*\]', address) 10 | 11 | if abba_regex.search(address): 12 | for found in hypernet: 13 | if abba_regex.search(found): 14 | return 15 | return True 16 | 17 | 18 | def check_ssl(address): 19 | """Check address has SLS support - ABA in supernet and related BAB in hypernet.""" 20 | in_squares = False 21 | supernet = [] 22 | hypernet = '' 23 | for char in address: 24 | if char == '[': 25 | in_squares = True 26 | supernet.append('|') 27 | if in_squares: 28 | hypernet += char 29 | else: 30 | supernet.append(char) 31 | if char == ']': 32 | in_squares = False 33 | 34 | to_check = ''.join(supernet).split('|') 35 | 36 | for string in to_check: 37 | i = 0 38 | abas = [] 39 | while i < len(string) - 2: 40 | if string[i] == string[i + 2] and string[i] != string[i + 1]: 41 | abas.append((string[i],string[i + 1])) 42 | i += 1 43 | 44 | for aba in abas: 45 | bab_regex = re.findall(r'{}{}{}'.format(aba[1], aba[0], aba[1]), hypernet) 46 | 47 | if bab_regex: 48 | return True 49 | 50 | 51 | with open('inputs/day_07.txt') as f: 52 | addresses = [address.strip() for address in f.readlines()] 53 | 54 | tls = 0 55 | ssl = 0 56 | for address in addresses: 57 | if check_tls(address): 58 | tls += 1 59 | if check_ssl(address): 60 | ssl += 1 61 | 62 | # Answer One 63 | print("IPs Supporting TLS:", tls) 64 | 65 | # Answer Two 66 | print("IPs Supporting SSL:", ssl) 67 | -------------------------------------------------------------------------------- /2021/day_10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 10 - Syntax Scoring""" 4 | 5 | 6 | with open('inputs/day_10.txt', 'r') as aoc_input: 7 | lines = [line.strip() for line in aoc_input.readlines()] 8 | 9 | match_dict = {'(': ')', '{': '}', '<': '>', '[': ']'} 10 | 11 | corrupted_chars = [] 12 | i = len(lines) 13 | while i >= 0: 14 | i -= 1 15 | open_chunk_symbols = [] 16 | for char in lines[i]: 17 | if char in match_dict.keys(): 18 | open_chunk_symbols.append(char) 19 | elif char != match_dict[open_chunk_symbols.pop()]: 20 | corrupted_chars.append(char) 21 | del lines[i] 22 | break 23 | 24 | illegal_char_points = {')': 3, ']': 57, '}': 1197, '>': 25137} 25 | syntax_error_score = 0 26 | for char in corrupted_chars: 27 | syntax_error_score += illegal_char_points[char] 28 | 29 | # Answer One 30 | print("Total syntax error score:", syntax_error_score) 31 | 32 | completion_strings = [] 33 | for line in lines: 34 | open_chunk_symbols = [] 35 | for char in line: 36 | if char in match_dict.keys(): 37 | open_chunk_symbols.append(char) 38 | else: 39 | open_chunk_symbols.pop() 40 | 41 | finishing_chars = '' 42 | for remaining in open_chunk_symbols: 43 | finishing_chars = match_dict[remaining] + finishing_chars 44 | 45 | completion_strings.append(finishing_chars) 46 | 47 | 48 | completion_char_points = {')': 1, ']': 2, '}': 3, '>': 4} 49 | completion_scores = [] 50 | for completion_string in completion_strings: 51 | completion_score = 0 52 | for char in completion_string: 53 | completion_score *= 5 54 | completion_score += completion_char_points[char] 55 | 56 | completion_scores.append(completion_score) 57 | 58 | # Answer Two 59 | print("Middle completion score:", 60 | sorted(completion_scores)[len(completion_scores) // 2]) 61 | -------------------------------------------------------------------------------- /2015/day_14.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 14 - Reindeer Olympics""" 2 | 3 | import re 4 | 5 | 6 | def calc_distance(speed, run_duration, rest_duration, name): 7 | """Calculates the distance a Reindeer travels in a given time.""" 8 | splits = [name] 9 | distance = 0 10 | time = 0 11 | running = True 12 | elapsed = 0 13 | while time < race_duration: 14 | if running: 15 | distance += speed 16 | elapsed += 1 17 | if elapsed == run_duration: 18 | running = False 19 | elapsed = 0 20 | 21 | else: 22 | elapsed += 1 23 | if elapsed == rest_duration: 24 | running = True 25 | elapsed = 0 26 | 27 | splits.append(distance) 28 | time += 1 29 | 30 | tracking.append(splits) 31 | return distance 32 | 33 | 34 | with open('inputs/day_14.txt') as f: 35 | reins = f.readlines() 36 | 37 | race_duration = 2503 38 | reindeers = {} 39 | tracking = [] 40 | 41 | longest = 0 42 | for rein in reins: 43 | name = re.search(r'(\w+) can', rein).group(1) 44 | reindeers[name] = 0 45 | speed = int(re.search(r'(\d+) km/s', rein).group(1)) 46 | duration = int(re.search(r'(\d+) seconds,', rein).group(1)) 47 | rest = int(re.search(r'(\d+) seconds\.', rein).group(1)) 48 | 49 | distance = calc_distance(speed, duration, rest, name) 50 | if distance > longest: 51 | longest = distance 52 | 53 | # Answer Part One 54 | print("Longest Distance Travelled =", longest) 55 | 56 | 57 | for second in range(1, race_duration + 1): 58 | furthest = 0 59 | leader = '' 60 | for reindeer in tracking: 61 | if reindeer[second] > furthest: 62 | furthest = reindeer[second] 63 | leader = reindeer[0] 64 | 65 | reindeers[leader] += 1 66 | 67 | # Answer Part Two 68 | most_points = max(reindeers.values()) 69 | print("Most Accumulated Points =", most_points) 70 | -------------------------------------------------------------------------------- /2017/day_07.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 7 - Recursive Circus""" 2 | 3 | import collections 4 | import re 5 | 6 | 7 | def base_dict(tower_data): 8 | """Build weight and holding dictionaries and identify the base program.""" 9 | holding = {} 10 | weights = {} 11 | base = [] 12 | for tower in tower_data: 13 | program, weight, *held = re.findall(r'\w+', tower) 14 | base.append(program) 15 | weights[program] = int(weight) 16 | holding[program] = list(held) 17 | 18 | for __, values in holding.items(): 19 | [base.remove(value) for value in values] 20 | 21 | return (holding, weights, base[0]) 22 | 23 | 24 | def find_fix(program): 25 | """Traverse structure and find mismatched weight and the fix required.""" 26 | above = [find_fix(held) for held in holding[program]] 27 | 28 | if len(set(above)) == 2: 29 | # Imbalanced so find most common (correct) weight and aberrant one 30 | mismatch = collections.Counter(above).most_common() 31 | desired = mismatch[0][0] 32 | wrong = mismatch[1][0] 33 | # Find imbalanced program and the weight it should be 34 | imbalanced = holding[program][above.index(wrong)] 35 | corrected = weights[imbalanced] + (desired - wrong) 36 | 37 | # Answer Two 38 | print("Corrected weight:", corrected) 39 | 40 | # Return corrected weight so upstream imbalances don't trigger 41 | return weights[program] + sum(above) + desired - wrong 42 | 43 | return weights[program] + sum(above) 44 | 45 | 46 | if __name__ == '__main__': 47 | 48 | with open('inputs/day_07.txt', 'r') as f: 49 | data = f.readlines() 50 | 51 | initial_info = base_dict(data) 52 | holding = initial_info[0] 53 | weights = initial_info[1] 54 | base = initial_info[2] 55 | 56 | # Answer One 57 | print("Bottom program:", base) 58 | 59 | # Answer Two 60 | find_fix(base) 61 | -------------------------------------------------------------------------------- /2020/day_05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 05 - Binary Binding.""" 4 | 5 | 6 | def decode_binary_space_partition(code: str) -> tuple: 7 | """Decode binary partition character code and return (row, col) tuple. 8 | 9 | Args: 10 | code: A 9 character code - The first 7 characters are each either 11 | "F"ront or "B"ack and the last 3 are either "L"eft or "R"ight. 12 | 13 | Returns: 14 | tuple consisting of two integers representing row and column. 15 | 16 | """ 17 | row_low = col_low = 0 18 | row_high = 127 19 | col_high = 7 20 | for char in code: 21 | if char == "F": 22 | row_high -= (row_high - row_low) // 2 + 1 23 | elif char == "B": 24 | row_low += (row_high - row_low) // 2 + 1 25 | elif char == "L": 26 | col_high -= (col_high - col_low) // 2 + 1 27 | elif char == "R": 28 | col_low += (col_high - col_low) // 2 + 1 29 | 30 | row = row_low if code[6] == "F" else row_high 31 | 32 | col = col_low if code[9] == "L" else col_high 33 | 34 | return (row, col) 35 | 36 | 37 | with open ('inputs/day_05.txt', 'r') as scanned: 38 | seat_codes = [seat_code.strip() for seat_code in scanned.readlines()] 39 | 40 | seats = set() 41 | seat_ids = set() 42 | for seat_code in seat_codes: 43 | row, col = decode_binary_space_partition(seat_code) 44 | seats.add((row, col)) 45 | seat_id = row * 8 + col 46 | seat_ids.add(seat_id) 47 | 48 | # Answer One 49 | print("Highest seat ID scanned:", max(seat_ids)) 50 | 51 | my_seat_id = None 52 | for row in range(1, 127): 53 | for col in range(0, 8): 54 | seat_id = row * 8 + col 55 | if (row, col) not in seats: 56 | if seat_id + 1 in seat_ids and seat_id - 1 in seat_ids: 57 | my_seat_id = seat_id 58 | break 59 | 60 | if my_seat_id: 61 | break 62 | 63 | # Answer Two 64 | print("ID of my seat:", my_seat_id) 65 | -------------------------------------------------------------------------------- /2015/day_03.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 3 - Perfectly Spherical Houses in a Vacuum""" 2 | 3 | 4 | def houses_visited(part_two=False): 5 | """Returns the number of unique houses visited.""" 6 | visited = 1 7 | visited_list = [[0, 0]] 8 | santa_coords = [0, 0] 9 | robo_coords = [0, 0] 10 | santa = True 11 | for char in directions: 12 | if santa: 13 | if char == '^': 14 | new_coords = [santa_coords[0], santa_coords[1] + 1] 15 | elif char == 'v': 16 | new_coords = [santa_coords[0], santa_coords[1] - 1] 17 | elif char == '<': 18 | new_coords = [santa_coords[0] - 1, santa_coords[1]] 19 | elif char == '>': 20 | new_coords = [santa_coords[0] + 1, santa_coords[1]] 21 | 22 | if new_coords not in visited_list: 23 | visited_list.append(new_coords) 24 | visited += 1 25 | 26 | santa_coords = new_coords 27 | if part_two: 28 | santa = False 29 | 30 | else: 31 | if char == '^': 32 | new_coords = [robo_coords[0], robo_coords[1] + 1] 33 | elif char == 'v': 34 | new_coords = [robo_coords[0], robo_coords[1] - 1] 35 | elif char == '<': 36 | new_coords = [robo_coords[0] - 1, robo_coords[1]] 37 | elif char == '>': 38 | new_coords = [robo_coords[0] + 1, robo_coords[1]] 39 | 40 | if new_coords not in visited_list: 41 | visited_list.append(new_coords) 42 | visited += 1 43 | 44 | robo_coords = new_coords 45 | santa = True 46 | 47 | return visited 48 | 49 | 50 | with open('inputs/day_03.txt') as f: 51 | directions = f.read() 52 | 53 | # Answer Part One 54 | print("Number of unique houses visited =", houses_visited()) 55 | 56 | # Answer Part Two 57 | print("Number visited with robots help =", houses_visited(part_two=True)) 58 | -------------------------------------------------------------------------------- /2017/day_15.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 15 - Dueling Generators""" 2 | 3 | 4 | def judgement(seed_a, seed_b): 5 | """Return amount of times last 16 binary digits of generators match.""" 6 | sample = 0 7 | count = 0 8 | while sample <= 40000000: 9 | new_a = seed_a * 16807 % 2147483647 10 | new_b = seed_b * 48271 % 2147483647 11 | 12 | bin_a = bin(new_a) 13 | bin_b = bin(new_b) 14 | 15 | last16_a = bin_a[-16:] 16 | last16_b = bin_b[-16:] 17 | 18 | if last16_a == last16_b: 19 | count += 1 20 | 21 | seed_a = new_a 22 | seed_b = new_b 23 | sample += 1 24 | 25 | return count 26 | 27 | 28 | def aligned(seed_a, seed_b): 29 | """Return amount of times last 16 binary digits of aligned generators match.""" 30 | sample = 0 31 | count = 0 32 | 33 | while sample < 5000000: 34 | gen_a = False 35 | gen_b = False 36 | 37 | while gen_a is False and gen_b is False: 38 | 39 | while gen_a is False: 40 | new_a = seed_a * 16807 % 2147483647 41 | 42 | if new_a % 4 == 0: 43 | gen_a = True 44 | 45 | else: 46 | seed_a = new_a 47 | 48 | 49 | while gen_b is False: 50 | new_b = seed_b * 48271 % 2147483647 51 | 52 | if new_b % 8 == 0: 53 | gen_b = True 54 | 55 | else: 56 | seed_b = new_b 57 | 58 | bin_a = bin(new_a) 59 | bin_b = bin(new_b) 60 | 61 | last16_a = bin_a[-16:] 62 | last16_b = bin_b[-16:] 63 | 64 | if last16_a == last16_b: 65 | count += 1 66 | 67 | seed_a = new_a 68 | seed_b = new_b 69 | sample += 1 70 | 71 | return count 72 | 73 | 74 | # Answer One 75 | print("Final count:", judgement(116, 299)) 76 | 77 | # Answer Two 78 | print("Final count when the generators are aligned:", aligned(116, 299)) 79 | -------------------------------------------------------------------------------- /2018/day_20.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 20 - A Regular Map""" 2 | 3 | 4 | def cardinal_moves(coords, char, facility): 5 | """Process basic cardinal moves and update facility.""" 6 | x, y = coords 7 | if char == 'N': 8 | facility[(x, y-1)] = '-' 9 | facility[(x, y-2)] = '.' 10 | new_coords = (x, y-2) 11 | 12 | elif char == 'E': 13 | facility[(x+1, y)] = '|' 14 | facility[(x+2, y)] = '.' 15 | new_coords = (x+2, y) 16 | 17 | elif char == 'S': 18 | facility[(x, y+1)] = '-' 19 | facility[(x, y+2)] = '.' 20 | new_coords = (x, y+2) 21 | 22 | elif char == 'W': 23 | facility[(x-1, y)] = '|' 24 | facility[(x-2, y)] = '.' 25 | new_coords = (x-2, y) 26 | 27 | return new_coords 28 | 29 | 30 | def main(): 31 | """Generate facility through regex, tracking distance to each room.""" 32 | with open('inputs/day_20.txt') as f: 33 | regex = f.read().strip() 34 | 35 | coords = (0, 0) 36 | facility = {coords: 'X'} 37 | sections = [] 38 | distances = {} 39 | distance = 0 40 | for char in regex[1:-1]: 41 | 42 | if char in ('N', 'E', 'S', 'W'): 43 | coords = cardinal_moves(coords, char, facility) 44 | distance += 1 45 | 46 | current_distance = distances.get(coords, 1000000) 47 | if current_distance > distance: 48 | distances[coords] = distance 49 | 50 | elif char == '(': 51 | sections.append(coords) 52 | 53 | elif char == '|': 54 | coords = sections[-1] 55 | distance = distances[coords] 56 | 57 | elif char == ')': 58 | sections.pop() 59 | 60 | 61 | # Answer One 62 | print("Doors to furthest:", max(distances.values())) 63 | 64 | # Answer Two len 65 | over_1000 = len([x for x in distances.values() if x >= 1000]) 66 | print("Rooms 1000 doors away:", over_1000) 67 | 68 | 69 | if __name__ == '__main__': 70 | main() 71 | -------------------------------------------------------------------------------- /2022/day_02/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | type Strategy struct { 12 | opponentsMove string 13 | playerMove string 14 | } 15 | 16 | type PlayerMove struct { 17 | matchesWith string 18 | beats string 19 | pointValue int 20 | } 21 | 22 | func partOne(rounds []Strategy) int { 23 | moves := map[string]PlayerMove{ 24 | "X": {matchesWith: "A", beats: "C", pointValue: 1}, 25 | "Y": {matchesWith: "B", beats: "A", pointValue: 2}, 26 | "Z": {matchesWith: "C", beats: "B", pointValue: 3}, 27 | } 28 | totalPoints := 0 29 | for _, round := range rounds { 30 | move := moves[round.playerMove] 31 | if move.matchesWith == round.opponentsMove { 32 | totalPoints += move.pointValue + 3 33 | } else if move.beats == round.opponentsMove { 34 | totalPoints += move.pointValue + 6 35 | } else { 36 | totalPoints += move.pointValue 37 | } 38 | } 39 | 40 | return totalPoints 41 | } 42 | 43 | func partTwo(rounds []Strategy) int { 44 | opponentsMoveToPoints := map[string]map[string]int{ 45 | "A": {"X": 3, "Y": 4, "Z": 8}, 46 | "B": {"X": 1, "Y": 5, "Z": 9}, 47 | "C": {"X": 2, "Y": 6, "Z": 7}, 48 | } 49 | totalPoints := 0 50 | for _, round := range rounds { 51 | totalPoints += opponentsMoveToPoints[round.opponentsMove][round.playerMove] 52 | } 53 | 54 | return totalPoints 55 | } 56 | 57 | func main() { 58 | file, err := os.Open("input.txt") 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | defer file.Close() 63 | 64 | rounds := []Strategy{} 65 | scanner := bufio.NewScanner(file) 66 | for scanner.Scan() { 67 | moves := strings.Split(scanner.Text(), " ") 68 | round := Strategy{opponentsMove: moves[0], playerMove: moves[1]} 69 | rounds = append(rounds, round) 70 | } 71 | if err := scanner.Err(); err != nil { 72 | log.Fatal(err) 73 | } 74 | 75 | answerOne := partOne(rounds) 76 | fmt.Printf("Answer One: %d\n", answerOne) 77 | 78 | answerTwo := partTwo(rounds) 79 | fmt.Printf("Answer Two: %d\n", answerTwo) 80 | } 81 | -------------------------------------------------------------------------------- /2015/day_18.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 18 - Like a GIF For Your Yard""" 2 | 3 | 4 | def light_show(part_two=False): 5 | lights = {} 6 | for num, line in enumerate(light_lines): 7 | for pos, light in enumerate(line): 8 | lights['{},{}'.format(pos, num)] = light 9 | steps = 0 10 | while steps < 100: 11 | new_lights = {} 12 | for light, state in lights.items(): 13 | 14 | coords = light.split(',') 15 | x = int(coords[0]) 16 | y = int(coords[1]) 17 | 18 | adjacent = [str(x - 1) + ',' + str(y), str(x + 1) + ',' + str(y), 19 | str(x) + ',' + str(y - 1), str(x) + ',' + str(y + 1), 20 | str(x - 1) + ',' + str(y - 1), str(x - 1) + ',' + str(y + 1), 21 | str(x + 1) + ',' + str(y - 1), str(x + 1) + ',' + str(y + 1),] 22 | 23 | adjacent_on = 0 24 | for to_check in adjacent: 25 | if lights.get(to_check, '.') == '#': 26 | adjacent_on += 1 27 | 28 | if state == '#': 29 | if adjacent_on in (2, 3): 30 | new_lights['{},{}'.format(x, y)] = '#' 31 | 32 | else: 33 | new_lights['{},{}'.format(x, y)] = '.' 34 | 35 | elif state == '.': 36 | if adjacent_on == 3: 37 | new_lights['{},{}'.format(x, y)] = '#' 38 | else: 39 | new_lights['{},{}'.format(x, y)] = '.' 40 | 41 | if part_two: 42 | for coords in ('0,0', '0,99', '99,0', '99,99',): 43 | new_lights[coords] = '#' 44 | 45 | lights = new_lights 46 | 47 | steps += 1 48 | 49 | on = 0 50 | for state in lights.values(): 51 | if state == '#': 52 | on += 1 53 | 54 | return on 55 | 56 | 57 | with open('inputs/day_18.txt') as f: 58 | light_lines = [line.strip() for line in f] 59 | 60 | # Answer One 61 | print(light_show()) 62 | 63 | # Answer Two 64 | print(light_show(part_two=True)) 65 | -------------------------------------------------------------------------------- /2017/day_25.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 25 - The Halting Problem""" 2 | 3 | import re 4 | 5 | def make_dict(): 6 | """Create a nested dictionary representing state and actions to take.""" 7 | states = {} 8 | for section in sections[1:]: 9 | lines = section.split('\n') 10 | 11 | state = lines[0][-2] 12 | value_0 = lines[2][-2] 13 | dir_0 = re.search(r'(right|left)', lines[3]).group(1) 14 | cont_0 = lines[4][-2] 15 | value_1 = lines[6][-2] 16 | dir_1 = re.search(r'(right|left)', lines[7]).group(1) 17 | cont_1 = lines[8][-2] 18 | 19 | states[state + '0'] = {'write': value_0, 'dir': dir_0, 'cont': cont_0} 20 | states[state + '1'] = {'write': value_1, 'dir': dir_1, 'cont': cont_1} 21 | 22 | return states 23 | 24 | 25 | def run_turing(start_state, steps): 26 | """Use blueprint to run turing machine and generate diagnostic checksum.""" 27 | tape = {0: '0'} 28 | tape_pos = 0 29 | state = start_state 30 | for __ in range(steps): 31 | value = tape[tape_pos] 32 | write = states['{}{}'.format(state, value)]['write'] 33 | direction = states['{}{}'.format(state, value)]['dir'] 34 | new_state = states['{}{}'.format(state, value)]['cont'] 35 | tape[tape_pos] = write 36 | 37 | if direction == 'right': 38 | tape_pos += 1 39 | else: 40 | tape_pos -= 1 41 | tape[tape_pos] = tape.get(tape_pos, '0') 42 | 43 | state = new_state 44 | 45 | checksum = 0 46 | for __, value in tape.items(): 47 | if value == '1': 48 | checksum += 1 49 | 50 | return checksum 51 | 52 | 53 | if __name__ == '__main__': 54 | 55 | with open('inputs/day_25.txt') as f: 56 | blueprints = f.read() 57 | 58 | sections = blueprints.strip().split('\n\n') 59 | begin = re.search(r'state (\w)', sections[0]).group(1) 60 | steps = int(re.search(r'(\d+)', sections[0]).group(1)) 61 | 62 | states = make_dict() 63 | 64 | # Answer 65 | print("Diagnostic checksum:", run_turing(begin, steps)) 66 | -------------------------------------------------------------------------------- /2018/day_03.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 3 - No Matter How You Slice It""" 2 | 3 | import re 4 | 5 | 6 | def coordinate_cloth(side_length): 7 | """Create a coordinate dictionary representing cloth of specified size.""" 8 | cloth = {} 9 | row = 0 10 | while row < side_length: 11 | col = 0 12 | while col < side_length: 13 | cloth[(row, col)] = set() 14 | col += 1 15 | row += 1 16 | 17 | return cloth 18 | 19 | 20 | def map_claims(claims, cloth): 21 | """Map ids to their claims on the cloth's coordinates.""" 22 | claim_regex = re.compile(r'#(\d+) @ (\d+),(\d+): (\d+)x(\d+)') 23 | for claim in claims: 24 | info = re.match(claim_regex, claim) 25 | id_num = int(info.group(1)) 26 | start_col, start_row = int(info.group(2)), int(info.group(3)) 27 | width, height = int(info.group(4)), int(info.group(5)) 28 | 29 | row = start_row 30 | while row < start_row + height: 31 | col = start_col 32 | while col < start_col + width: 33 | cloth[row, col].add(id_num) 34 | col += 1 35 | row += 1 36 | 37 | 38 | def overlaps_and_intact(mapped_cloth): 39 | """Take mapped cloth return No. of overlaps and fully intact claim id.""" 40 | ids, unintact_ids = set(), set() 41 | overlapped = 0 42 | for value in mapped_cloth.values(): 43 | if len(value) > 1: 44 | ids.update(value) 45 | unintact_ids.update(value) 46 | overlapped += 1 47 | else: 48 | ids.update(value) 49 | 50 | intact = (ids - unintact_ids).pop() 51 | return (overlapped, intact) 52 | 53 | 54 | if __name__ == '__main__': 55 | 56 | cloth = coordinate_cloth(1000) 57 | 58 | with open('inputs/day_03.txt') as f: 59 | claims = f.readlines() 60 | 61 | map_claims(claims, cloth) 62 | overlapped, intact = overlaps_and_intact(cloth) 63 | 64 | # Answer One 65 | print("Number of overlapping sections:", overlapped) 66 | 67 | # Answer Two 68 | print("Intact area id:", intact) 69 | -------------------------------------------------------------------------------- /2022/day_12/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func bfs(start image.Point, endValue rune, heightMap map[image.Point]rune, partOne bool) int { 11 | deltas := []image.Point{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} 12 | 13 | queue := []image.Point{start} 14 | steps := map[image.Point]int{start: 0} 15 | for len(queue) > 0 { 16 | current := queue[0] 17 | queue = queue[1:] 18 | currentHeight := heightMap[current] 19 | 20 | for _, delta := range deltas { 21 | neighbour := current.Add(delta) 22 | 23 | _, visited := steps[neighbour] 24 | if visited { 25 | continue 26 | } 27 | 28 | neighbourHeight, valid := heightMap[neighbour] 29 | if !valid { 30 | continue 31 | } 32 | 33 | if partOne { 34 | if neighbourHeight <= currentHeight+1 { 35 | queue = append(queue, neighbour) 36 | steps[neighbour] = steps[current] + 1 37 | } else { 38 | continue 39 | } 40 | } else { 41 | if neighbourHeight >= currentHeight-1 { 42 | queue = append(queue, neighbour) 43 | steps[neighbour] = steps[current] + 1 44 | } else { 45 | continue 46 | } 47 | } 48 | 49 | if neighbourHeight == endValue { 50 | return steps[current] + 1 51 | } 52 | } 53 | } 54 | 55 | return -1 56 | } 57 | 58 | func main() { 59 | file, _ := os.ReadFile("input.txt") 60 | rows := strings.Split(string(file), "\n") 61 | heightMap := map[image.Point]rune{} 62 | var endCoords image.Point 63 | var startCoords image.Point 64 | for y, row := range rows { 65 | for x, rune := range row { 66 | point := image.Point{x, y} 67 | heightMap[point] = rune 68 | if rune == 'E' { 69 | endCoords = point 70 | } 71 | if rune == 'S' { 72 | startCoords = point 73 | heightMap[point] = 'a' 74 | } 75 | } 76 | } 77 | 78 | fewestSteps := bfs(startCoords, heightMap[endCoords], heightMap, true) 79 | fmt.Println("Answer One:", fewestSteps) 80 | 81 | heightMap[endCoords] = 'z' 82 | fewestSteps = bfs(endCoords, heightMap[startCoords], heightMap, false) 83 | fmt.Println("Answer Two:", fewestSteps) 84 | } 85 | -------------------------------------------------------------------------------- /2018/day_12.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 12 - Subterranean Sustainability""" 2 | 3 | 4 | def generations(pots, notes): 5 | """Return next generation of pots after rules in notes.""" 6 | next_gen = [] 7 | for pot in pots.keys(): 8 | pattern = '' 9 | for i in range(-2, 3): 10 | pattern += pots.get(pot + i, '.') 11 | 12 | next_gen.append((pot, notes[pattern])) 13 | 14 | # Check edges for new pots 15 | left = min(pots) 16 | pattern = '' 17 | for i in range(left - 3, left + 2): 18 | pattern += pots.get(i, '.') 19 | if notes[pattern] == '#': 20 | pots[left - 1] = '#' 21 | 22 | right = max(pots) 23 | pattern = '' 24 | for i in range(right - 1, right + 4): 25 | pattern += pots.get(i, '.') 26 | if notes[pattern] == '#': 27 | pots[right + 1] = '#' 28 | 29 | for change in next_gen: 30 | pots[change[0]] = change[1] 31 | 32 | 33 | with open('inputs/day_12.txt') as f: 34 | lines = f.readlines() 35 | 36 | initial_state = list(lines[0].split()[2]) 37 | pots = dict(zip(range(0, len(initial_state)), initial_state)) 38 | 39 | notes_parse = [line.strip().split() for line in lines[2:]] 40 | notes = {} 41 | for note in notes_parse: 42 | pattern, __, result = note 43 | notes[pattern] = result 44 | 45 | differences = [0, 0, 0, 0, 1] 46 | prev_plants = 0 47 | i = 1 48 | while i < 1000: 49 | generations(pots, notes) 50 | plants = 0 51 | for pot, state in pots.items(): 52 | if state == '#': 53 | plants += pot 54 | 55 | if i == 20: 56 | # Answer One 57 | print("Plants after 20:", plants) 58 | 59 | # Check if differences between generations has stabilised 60 | difference = plants - prev_plants 61 | del differences[0] 62 | differences.append(difference) 63 | 64 | if len(set(differences)) == 1: 65 | remaining = 5*10**10 - i 66 | total_plants = plants + difference * remaining 67 | # Answer Two 68 | print("Plants after 50 billion:", total_plants) 69 | break 70 | 71 | prev_plants = plants 72 | i += 1 73 | -------------------------------------------------------------------------------- /2021/day_20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 20 - Trench Map""" 4 | 5 | 6 | def enhance_image(image_coords, steps): 7 | explored = set() 8 | for step in range(steps): 9 | x_values = sorted([coords[0] for coords in image_coords]) 10 | y_values = sorted([coords[1] for coords in image_coords]) 11 | min_x, max_x = x_values[0] - 1, x_values[-1] + 1 12 | min_y, max_y = y_values[0] - 1, y_values[-1] + 1 13 | 14 | new_image = set() 15 | for y in range(min_y, max_y + 1): 16 | 17 | for x in range(min_x, max_x + 1): 18 | 19 | if not step % 2: 20 | explored.add((x, y)) 21 | 22 | pixels = [ 23 | (x - 1, y - 1), (x, y - 1 ), (x + 1, y - 1), 24 | (x - 1, y), (x, y), (x + 1, y), 25 | (x - 1, y + 1), (x, y + 1), (x + 1, y + 1) 26 | ] 27 | 28 | binary = '' 29 | for pixel in pixels: 30 | if step % 2 and pixel not in explored: 31 | binary += '1' 32 | else: 33 | binary += '1' if pixel in image_coords else '0' 34 | 35 | new_value = algo[int(binary, 2)] 36 | if new_value == '#': 37 | new_image.add((x, y)) 38 | 39 | image_coords = new_image 40 | if step % 2: 41 | explored = set() 42 | 43 | return image_coords 44 | 45 | 46 | with open('inputs/day_20.txt', 'r') as aoc_input: 47 | algo, image = [x.strip() for x in aoc_input.read().split('\n\n')] 48 | 49 | image_coords = set() 50 | for row_num, row in enumerate(image.split('\n')): 51 | for col_num, value in enumerate(row): 52 | if value == '#': 53 | image_coords.add((col_num, row_num)) 54 | 55 | # Answer One 56 | print("Number of pixels lit in resulting image:", 57 | len(enhance_image(image_coords, 2))) 58 | 59 | # Answer Two 60 | print("Number of pixels lit in resulting image:", 61 | len(enhance_image(image_coords, 50))) 62 | -------------------------------------------------------------------------------- /2016/day_04.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 4 - Security Through Obscurity""" 2 | 3 | import re 4 | 5 | def check_checksum(room): 6 | """Calculate checksum returning Sector ID if it matches the encoded one.""" 7 | checksum = re.search(r'\[(\w+)]', room).group(1) 8 | sector_id = int(re.search(r'(\d+)', room).group(1)) 9 | letters = set(re.findall(r'([^-0-9])', room[:-(len(checksum) + 2)])) 10 | frequencies = {} 11 | 12 | for letter in letters: 13 | frequencies[letter] = room.count(letter) 14 | 15 | sorted_freq = sorted(frequencies.items(), key=lambda kv: kv[1], reverse=True) 16 | 17 | calc_checksum = [] 18 | for i in range(sorted_freq[0][1], 1, -1): 19 | top = [] 20 | for freq_tuple in sorted_freq: 21 | if freq_tuple[1] == i: 22 | top.append(freq_tuple[0]) 23 | else: 24 | continue 25 | 26 | sorted_top = sorted(top) 27 | [calc_checksum.append(letter) for letter in sorted_top] 28 | if len(calc_checksum) == 5: 29 | break 30 | 31 | real_checksum = ''.join(calc_checksum[:5]) 32 | 33 | if checksum == real_checksum: 34 | return sector_id 35 | 36 | return 0 37 | 38 | 39 | def decrypt_name(room): 40 | """Decrypt shift cypher using Sector ID returning it if 'north' in decrypt.""" 41 | sector_id = int(re.search(r'(\d+)', room).group(1)) 42 | alphabet = 'abcdefghijklmnopqrstuvwxyz' 43 | decrypted = '' 44 | for char in room: 45 | if char == '-': 46 | decrypted += ' ' 47 | elif char in alphabet: 48 | new_char = alphabet[(alphabet.index(char) + sector_id) % 26] 49 | decrypted += new_char 50 | 51 | if 'north' in decrypted: 52 | return sector_id 53 | 54 | return 0 55 | 56 | 57 | with open('inputs/day_04.txt', 'r') as f: 58 | rooms = [line.strip() for line in f.readlines()] 59 | 60 | # Answer One 61 | print("Sum of Valid Rooms:", sum([check_checksum(room) for room in rooms])) 62 | 63 | # Answer Two 64 | print("Sector ID of North Pole Storage:", sum(decrypt_name(room) for room in rooms)) 65 | -------------------------------------------------------------------------------- /2021/day_14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 14 - Extended Polymerization""" 4 | 5 | 6 | with open('inputs/day_14.txt', 'r') as aoc_input: 7 | template, pairs = aoc_input.read().split('\n\n') 8 | 9 | reactions = {} 10 | for pair in pairs.strip().split('\n'): 11 | reactants, insertion = pair.split(' -> ') 12 | reactions[reactants] = insertion 13 | 14 | polymer = template.strip() 15 | for _ in range(10): 16 | new_polymer = '' 17 | for index, element in enumerate(polymer[:-1]): 18 | to_insert = reactions[f'{element}{polymer[index + 1]}'] 19 | new_polymer += f'{element}{to_insert}' 20 | 21 | new_polymer += template.strip()[-1] 22 | polymer = new_polymer 23 | 24 | element_count = {} 25 | for element in polymer: 26 | element_count[element] = element_count.get(element, 0) + 1 27 | 28 | most_minus_least = max(element_count.values()) - min(element_count.values()) 29 | 30 | # Answer One 31 | print("Quantity of most common element minus least common:", most_minus_least) 32 | 33 | pairs = {} 34 | polymer = template.strip() 35 | for index, element in enumerate(polymer[:-1]): 36 | pair = f'{element}{polymer[index + 1]}' 37 | pairs[pair] = pairs.get(pair, 0) + 1 38 | 39 | for _ in range(40): 40 | new_pairs = {} 41 | for pair in pairs: 42 | element_one, element_two = pair 43 | to_insert = reactions[pair] 44 | first_new = f'{element_one}{to_insert}' 45 | second_new = f'{to_insert}{element_two}' 46 | 47 | new_pairs[first_new] = pairs[pair] + new_pairs.get(first_new, 0) 48 | new_pairs[second_new] = pairs[pair] + new_pairs.get(second_new, 0) 49 | 50 | pairs = new_pairs 51 | 52 | element_count = {} 53 | for pair, count in pairs.items(): 54 | element_count[pair[0]] = element_count.get(pair[0], 0) + count 55 | 56 | # Last element stays same throughout but isn't counted in above 57 | element_count[polymer[-1]] += 1 58 | 59 | most_minus_least = max(element_count.values()) - min(element_count.values()) 60 | 61 | # Answer Two 62 | print("Quantity of most common element minus least common:", most_minus_least) 63 | -------------------------------------------------------------------------------- /2016/day_14.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 14 - One-Time Pad""" 2 | 3 | import re 4 | import hashlib 5 | 6 | def key_stretch(key): 7 | """Repeatedly hash a key then return it.""" 8 | hashed = 0 9 | while hashed < 2016: 10 | key = hashlib.md5('{}'.format(key).encode('utf-8')).hexdigest() 11 | hashed += 1 12 | return key 13 | 14 | 15 | def find_keys(salt, part_two=False): 16 | """Find hash - triple char with a pentuple of that char within 1000 hashes.""" 17 | hash_cache = {} 18 | keys = [] 19 | i = 0 20 | while len(keys) < 64: 21 | if i in hash_cache: 22 | hexed = hash_cache[i] 23 | else: 24 | hexed = hashlib.md5('{}{}'.format(salt, i).encode('utf-8')).hexdigest() 25 | if part_two: 26 | hexed = key_stretch(hexed) 27 | 28 | hash_cache[i] = hexed 29 | 30 | triple_regex = re.search(r'(\w)\1{2}', hexed) 31 | key = False 32 | if triple_regex: 33 | repeated = triple_regex.group(1) 34 | 35 | for num in range(0, i): 36 | if num in hash_cache: 37 | del hash_cache[num] 38 | 39 | j = 1 40 | while j < 1000: 41 | if i + j in hash_cache: 42 | subhex = hash_cache[i + j] 43 | else: 44 | subhex = hashlib.md5('{}{}'.format(salt, i + j).encode('utf-8')).hexdigest() 45 | 46 | if part_two: 47 | subhex = key_stretch(subhex) 48 | 49 | hash_cache[i + j] = subhex 50 | 51 | pent_regex = re.search(r'({})\1\1\1\1'.format(repeated), subhex) 52 | if pent_regex: 53 | key = True 54 | break 55 | 56 | j += 1 57 | 58 | if key: 59 | keys.append(i) 60 | 61 | i += 1 62 | return(keys[-1]) 63 | 64 | 65 | salt_str = 'ngcjuoqr' 66 | 67 | # Answer One 68 | print("Index that generates the 64th key:", find_keys(salt_str)) 69 | 70 | # Answer Two 71 | print("Index with key stretching:", find_keys(salt_str, part_two=True)) 72 | -------------------------------------------------------------------------------- /2015/day_15.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 15 - Science for Hungry People""" 2 | 3 | import re 4 | 5 | 6 | with open('inputs/day_15.txt') as f: 7 | info = f.readlines() 8 | 9 | nutri_info = [] 10 | for ingredient in info: 11 | capacity = int(re.search(r'capacity ([0-9-]+)', ingredient).group(1)) 12 | durability = int(re.search(r'durability ([0-9-]+)', ingredient).group(1)) 13 | flavor = int(re.search(r'flavor ([0-9-]+)', ingredient).group(1)) 14 | texture = int(re.search(r'texture ([0-9-]+)', ingredient).group(1)) 15 | calories = int(re.search(r'calories ([0-9-]+)', ingredient).group(1)) 16 | 17 | nutri_info.append([capacity, durability, flavor, texture, calories]) 18 | 19 | ratios = [] 20 | for sugar in range(0, 101): 21 | for sprinkles in range(0, 101 - sugar): 22 | for candy in range(0, 101 - sprinkles - sugar): 23 | 24 | chocolate = 100 - (sugar + sprinkles + candy) 25 | ratios.append([sugar, sprinkles, candy, chocolate]) 26 | 27 | 28 | highest = 0 29 | for ratio in ratios: 30 | 31 | capacity = (ratio[0] * nutri_info[0][0] + ratio[1] * nutri_info[1][0] 32 | + ratio[2] * nutri_info[2][0] + ratio[3] * nutri_info[3][0]) 33 | 34 | durability = (ratio[0] * nutri_info[0][1] + ratio[1] * nutri_info[1][1] 35 | + ratio[2] * nutri_info[2][1] + ratio[3] * nutri_info[3][1]) 36 | 37 | flavor = (ratio[0] * nutri_info[0][2] + ratio[1] * nutri_info[1][2] 38 | + ratio[2] * nutri_info[2][2] + ratio[3] * nutri_info[3][2]) 39 | 40 | texture = (ratio[0] * nutri_info[0][3] + ratio[1] * nutri_info[1][3] 41 | + ratio[2] * nutri_info[2][3] + ratio[3] * nutri_info[3][3]) 42 | 43 | calories = (ratio[0] * nutri_info[0][4] + ratio[1] * nutri_info[1][4] 44 | + ratio[2] * nutri_info[2][4] + ratio[3] * nutri_info[3][4]) 45 | 46 | if capacity <= 0 or durability <= 0 or flavor <= 0 or texture <= 0: 47 | continue 48 | 49 | if calories != 500: # Comment out for Part One 50 | continue 51 | 52 | score = capacity * durability * flavor * texture 53 | 54 | if score > highest: 55 | highest = score 56 | 57 | print("Highest Cookie Score =", highest) 58 | -------------------------------------------------------------------------------- /2016/day_13.py: -------------------------------------------------------------------------------- 1 | """ Advent of Code Day 13 - A Maze of Twisty Little Cubicles""" 2 | 3 | import collections 4 | 5 | 6 | def build_maze(rows, columns, fav_num): 7 | """Calculate, contruct and return a dictionary of the cubicle maze.""" 8 | locations = {} 9 | for y in range(rows): 10 | for x in range(columns): 11 | 12 | bined = bin(x*x + 3*x + 2*x*y + y + y*y + fav_num) 13 | if bined.count('1') % 2 == 0: 14 | locations[x,y] = '.' 15 | else: 16 | locations[x,y] = '#' 17 | 18 | return locations 19 | 20 | 21 | def possible_moves(node): 22 | """Given a location return all of the possible adjacent moves.""" 23 | x = node[0] 24 | y = node[1] 25 | adjacent = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)] 26 | possible = [] 27 | for cubicle in adjacent: 28 | if cubicle in locations and locations[cubicle] == '.': 29 | possible.append(cubicle) 30 | return possible 31 | 32 | 33 | def main(root, target, part_two=False): 34 | """Orchestrate the BFS returning steps taken when target is reached.""" 35 | prev = set() 36 | prev.add(root) 37 | 38 | to_process = collections.deque(possible_moves(root)) 39 | [prev.add(node) for node in to_process] 40 | 41 | steps = 1 42 | 43 | while True: 44 | children = [] 45 | while to_process: 46 | [children.append(child) for child in possible_moves(to_process.pop()) 47 | if child not in prev] 48 | 49 | steps += 1 50 | 51 | for child in children: 52 | if child == target and not part_two: 53 | return steps 54 | 55 | prev.add(child) 56 | 57 | if part_two and steps == 50: 58 | return len(prev) 59 | 60 | to_process = collections.deque(children) 61 | 62 | 63 | if __name__ == '__main__': 64 | 65 | locations = build_maze(40, 35, 1362) 66 | # Answer One 67 | print("Least number of steps to get to target:", main((1, 1), (31, 39))) 68 | 69 | # Answer Two 70 | print("Number of unique locations visited after 50 steps:", 71 | main((1, 1), (31, 39), part_two=True )) 72 | -------------------------------------------------------------------------------- /2019/day_14.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 14 - Space Stoichiometry""" 2 | 3 | from collections import defaultdict 4 | import math 5 | 6 | 7 | def create_fuel(reactions, fuel_wanted): 8 | """Create wanted amount of fuel from reactions, return material dict.""" 9 | needed = defaultdict(int) 10 | needed['FUEL'] = fuel_wanted 11 | queue = ['FUEL'] 12 | while queue: 13 | target = queue.pop() 14 | 15 | for product, reactants in reactions.items(): 16 | if product[1] == target: 17 | quantity_produced = product[0] 18 | required = math.ceil(needed[target] / quantity_produced) 19 | 20 | for reactant in reactants: 21 | needed[reactant[1]] += required * reactant[0] 22 | 23 | if needed[target] > 0: 24 | queue.append(reactant[1]) 25 | 26 | needed[target] -= required * product[0] 27 | 28 | return needed 29 | 30 | 31 | with open('inputs/day_14.txt') as f: 32 | recipies = [line.strip() for line in f.readlines()] 33 | 34 | reactions = {} 35 | for recipie in recipies: 36 | reactants, product = [material.strip() for material in recipie.split('=>')] 37 | product = product.split() 38 | product = (int(product[0]), product[1]) 39 | 40 | reactants_list = [] 41 | reactants = reactants.split(', ') 42 | for reactant in reactants: 43 | amount, material = reactant.split(' ') 44 | reactants_list.append((int(amount), material)) 45 | 46 | reactions[product] = reactants_list 47 | 48 | # Answer One 49 | ore_needed = create_fuel(reactions, 1)['ORE'] 50 | print("Minimum amount of ore:", ore_needed) 51 | 52 | # Binary search for maxiumum fuel amount 53 | trillion_ore = 10 ** 12 54 | low = trillion_ore // ore_needed 55 | high = trillion_ore - 1 56 | while low <= high: 57 | mid = (low + high) // 2 58 | if low == mid: 59 | break 60 | 61 | ore_needed = create_fuel(reactions, mid)['ORE'] 62 | if ore_needed < trillion_ore: 63 | low = mid + 1 64 | else: 65 | high = mid - 1 66 | 67 | # Answer Two 68 | print("Most fuel that can be made from 1 trillion ore:", low) 69 | -------------------------------------------------------------------------------- /2020/day_19.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 19 - Monster Messages.""" 4 | 5 | 6 | import re 7 | 8 | 9 | def convert_to_regex(rules_dict: dict, rule_num: int, depth: int=0) -> str: 10 | """Convert rule to its regex equivalent and return it. 11 | 12 | Args: 13 | rules_dict: Dictionary of rule_num: rule. 14 | rule_num: Key for rules_dict corresponding to rule to be converted. 15 | depth: Number of recursive calls to the function. Defaults to 0 and 16 | used to set a limit on processing of loops. 17 | 18 | Returns: 19 | The regex string equivalent of the rule at key rule_num in rules_dict 20 | (to a capped depth). 21 | 22 | """ 23 | if depth > 15: 24 | return '' 25 | 26 | if rules_dict[rule_num] in ('a', 'b'): 27 | return rules_dict[rule_num] 28 | 29 | rules = [] 30 | for branches in rules_dict[rule_num].split('|'): 31 | branch_answer = '' 32 | for branch_num in branches.split(): 33 | branch_re = convert_to_regex(rules_dict, branch_num, depth + 1) 34 | branch_answer += branch_re 35 | rules.append(branch_answer) 36 | 37 | return '(' + '|'.join(rules) + ')' 38 | 39 | 40 | rules, messages = open('inputs/day_19.txt').read().split('\n\n') 41 | rules_dict = {} 42 | for line in rules.split("\n"): 43 | rule_num, rule = [x.strip() for x in line.split(':')] 44 | if rule[0] == '"': 45 | rule = rule.strip('"') 46 | rules_dict[rule_num] = rule 47 | 48 | regex = re.compile(convert_to_regex(rules_dict, '0')) 49 | 50 | matches = 0 51 | for message in messages.split(): 52 | if regex.fullmatch(message): 53 | matches += 1 54 | 55 | # Answer One 56 | print("Number of messages that completely match rule 0:", matches) 57 | 58 | rules_dict["8"] = "42 | 42 8" 59 | rules_dict["11"] = "42 31 | 42 11 31" 60 | 61 | regex = re.compile(convert_to_regex(rules_dict, '0')) 62 | 63 | matches = 0 64 | for message in messages.split(): 65 | if regex.fullmatch(message): 66 | matches += 1 67 | 68 | # Answer Two 69 | print("Number of messages that completely match rule 0 after update:", 70 | matches) 71 | -------------------------------------------------------------------------------- /2022/day_04/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type ElfPair struct { 13 | oneStart int 14 | oneEnd int 15 | twoStart int 16 | twoEnd int 17 | } 18 | 19 | func partOne(elfPairs []ElfPair) int { 20 | numCompleteOverlaps := 0 21 | for _, pair := range elfPairs { 22 | if pair.oneStart <= pair.twoStart && pair.oneEnd >= pair.twoEnd { 23 | numCompleteOverlaps++ 24 | } else if pair.twoStart <= pair.oneStart && pair.twoEnd >= pair.oneEnd { 25 | numCompleteOverlaps++ 26 | } 27 | } 28 | return numCompleteOverlaps 29 | } 30 | 31 | func partTwo(elfPairs []ElfPair) int { 32 | numOverlaps := 0 33 | for _, pair := range elfPairs { 34 | oneSet := map[int]bool{} 35 | for i := pair.oneStart; i <= pair.oneEnd; i++ { 36 | oneSet[i] = true 37 | } 38 | 39 | for i := pair.twoStart; i <= pair.twoEnd; i++ { 40 | if oneSet[i] { 41 | numOverlaps++ 42 | break 43 | } 44 | } 45 | } 46 | return numOverlaps 47 | } 48 | 49 | func main() { 50 | file, err := os.Open("input.txt") 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | defer file.Close() 55 | 56 | elfPairs := []ElfPair{} 57 | scanner := bufio.NewScanner(file) 58 | for scanner.Scan() { 59 | line := scanner.Text() 60 | elves := strings.Split(line, ",") 61 | elfOneRanges := strings.Split(elves[0], "-") 62 | elfTwoRanges := strings.Split(elves[1], "-") 63 | oneStart, err := strconv.Atoi(elfOneRanges[0]) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | oneEnd, err := strconv.Atoi(elfOneRanges[1]) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | twoStart, err := strconv.Atoi(elfTwoRanges[0]) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | twoEnd, err := strconv.Atoi(elfTwoRanges[1]) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | elfPairs = append(elfPairs, ElfPair{oneStart, oneEnd, twoStart, twoEnd}) 80 | } 81 | if err := scanner.Err(); err != nil { 82 | log.Fatal(err) 83 | } 84 | 85 | answerOne := partOne(elfPairs) 86 | fmt.Printf("Answer One: %d\n", answerOne) 87 | 88 | answerTwo := partTwo(elfPairs) 89 | fmt.Printf("Answer Two: %d\n", answerTwo) 90 | } 91 | -------------------------------------------------------------------------------- /2021/day_03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 3 - Binary Diagnostic""" 4 | 5 | 6 | def convert_to_columns(binary_values): 7 | """Takes list of strings and returns one with rows and columns swapped.""" 8 | column_conversion = [] 9 | for row_num in range(len(binary_values[0])): 10 | column_conversion.append('') 11 | for col_num in range(len(binary_values)): 12 | column_conversion[-1] += binary_values[col_num][row_num] 13 | 14 | return column_conversion 15 | 16 | 17 | with open('inputs/day_03.txt', 'r') as aoc_input: 18 | input_lines = [line.strip() for line in aoc_input.readlines()] 19 | 20 | binary_columns = convert_to_columns(input_lines) 21 | gamma = '' 22 | for column in binary_columns: 23 | if column.count('0') > len(column) / 2: 24 | gamma += '0' 25 | else: 26 | gamma += '1' 27 | 28 | epsilon = ''.join(map(lambda bit: '1' if bit == '0' else '0', gamma)) 29 | 30 | # Answer One 31 | print("Submarine power consumption:", int(gamma, 2) * int(epsilon, 2)) 32 | 33 | oxy_binaries = input_lines.copy() 34 | oxy_columns = binary_columns.copy() 35 | oxy_criteria = '' 36 | i = 0 37 | while len(oxy_binaries) > 1: 38 | if oxy_columns[i].count('1') >= len(oxy_columns[i]) / 2: 39 | oxy_criteria += '1' 40 | else: 41 | oxy_criteria += '0' 42 | 43 | oxy_binaries = list(filter( 44 | lambda binary: binary.startswith(oxy_criteria), oxy_binaries 45 | ) 46 | ) 47 | oxy_columns = convert_to_columns(oxy_binaries) 48 | 49 | i += 1 50 | 51 | co2_binary = input_lines.copy() 52 | co2_columns = binary_columns.copy() 53 | co2_criteria = '' 54 | i = 0 55 | while len(co2_binary) > 1: 56 | if co2_columns[i].count('1') >= len(co2_columns[i]) / 2: 57 | co2_criteria += '0' 58 | else: 59 | co2_criteria += '1' 60 | 61 | co2_binary = list(filter( 62 | lambda binary: binary.startswith(co2_criteria), co2_binary 63 | ) 64 | ) 65 | co2_columns = convert_to_columns(co2_binary) 66 | 67 | i += 1 68 | 69 | # Answer Two 70 | print("Submarine life support rating:", 71 | int(oxy_binaries[0], 2) * int(co2_binary[0], 2)) 72 | -------------------------------------------------------------------------------- /2018/day_25.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 25 - Four Dimensional Adventure""" 2 | 3 | 4 | class FixedPoint: 5 | """Initialise fixed point as ranked, self-parented node.""" 6 | def __init__(self, num): 7 | self.num = num 8 | self.parent = self 9 | self.rank = 0 10 | 11 | 12 | def find(point): 13 | """Return root of point (parent with parent as self).""" 14 | if point.parent != point: 15 | point.parent = find(point.parent) 16 | return point.parent 17 | 18 | 19 | def union(point_1, point_2): 20 | """Combine the sets containing each point via union by rank.""" 21 | # Find both roots and check they aren't already in the same set 22 | point_1_root = find(point_1) 23 | point_2_root = find(point_2) 24 | if point_1_root == point_2_root: 25 | return 26 | 27 | # Rank merge avoids unchecked O(n) growth of trees if done naively 28 | if point_1_root.rank < point_2_root.rank: 29 | point_1_root, point_2_root = point_2_root, point_1_root 30 | 31 | point_2_root.parent = point_1_root 32 | if point_1_root.rank == point_2_root.rank: 33 | point_1_root.rank += 1 34 | 35 | 36 | def manhattan_distance(point_1, point_2): 37 | """Return Manhattan distance between two points.""" 38 | return sum(abs(a - b) for a, b in zip(point_1, point_2)) 39 | 40 | 41 | def main(): 42 | """Use disjoint-set structure to turn points into constellations.""" 43 | with open('inputs/day_25.txt') as f: 44 | lines = [[int(x) for x in l.strip().split(',')] for l in f.readlines()] 45 | 46 | # Initialise each point and check against previous to see if union needed 47 | points = {} 48 | for i, line in enumerate(lines): 49 | coords = tuple(line) 50 | points[coords] = FixedPoint(i) 51 | 52 | for point in points: 53 | if manhattan_distance(coords, point) <= 3: 54 | union(points[coords], points[point]) 55 | 56 | # Set comprehension of find() for each FixedPoint gives unique roots 57 | constellations = len({find(x) for x in points.values()}) 58 | 59 | # Answer One 60 | print("Number of constellations:", constellations) 61 | 62 | 63 | if __name__ == '__main__': 64 | main() 65 | -------------------------------------------------------------------------------- /2020/day_14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 14 - Docking Data.""" 4 | 5 | 6 | from itertools import product 7 | import re 8 | 9 | 10 | with open ('inputs/day_14.txt', 'r') as f: 11 | rows = [row.strip() for row in f.readlines()] 12 | 13 | instructions = [] 14 | instruction_regex = re.compile('mem\[(\d*)\]\s=\s(\d*)') 15 | for row in rows: 16 | if row.startswith('mask'): 17 | instructions.append(row.split()[2]) 18 | else: 19 | mem, value = instruction_regex.findall(row)[0] 20 | instructions.append((mem, value)) 21 | 22 | memory = {} 23 | for instruction in instructions: 24 | if type(instruction) is str: 25 | mask = instruction 26 | else: 27 | mem, value = instruction 28 | value = format(int(value), '036b') 29 | masked_value = '' 30 | for index, bit in enumerate(mask): 31 | if bit == 'X': 32 | masked_value += value[index] 33 | else: 34 | masked_value += bit 35 | 36 | memory[mem] = int(masked_value, 2) 37 | 38 | # Answer One 39 | print("Sum of all values left in memory after initialisation:", 40 | sum(memory.values())) 41 | 42 | memory = {} 43 | for instruction in instructions: 44 | if type(instruction) is str: 45 | mask = instruction 46 | else: 47 | mem, value = instruction 48 | mem = format(int(mem), '036b') 49 | masked_mem = '' 50 | for index, bit in enumerate(mask): 51 | if bit == '0': 52 | masked_mem += mem[index] 53 | else: 54 | masked_mem += bit 55 | 56 | x_count = masked_mem.count('X') 57 | for floats in product(('0', '1'), repeat=x_count): 58 | float_mem = '' 59 | float_index = 0 60 | for bit in masked_mem: 61 | if bit == 'X': 62 | float_mem += floats[float_index] 63 | float_index += 1 64 | else: 65 | float_mem += bit 66 | 67 | memory[int(float_mem, 2)] = int(value) 68 | 69 | # Answer Two 70 | print("Sum of all values left in memory after Version 2 initialisation:", 71 | sum(memory.values())) 72 | -------------------------------------------------------------------------------- /2022/day_09/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type Move struct { 13 | direction string 14 | distance int 15 | } 16 | 17 | func moveTail(head *image.Point, tail *image.Point, movements map[string]image.Point) { 18 | xDist := head.X - tail.X 19 | yDist := head.Y - tail.Y 20 | 21 | if xDist > 1 || xDist < -1 || yDist > 1 || yDist < -1 { 22 | var xMove image.Point 23 | if xDist < 0 { 24 | xMove = movements["left"] 25 | } else if xDist > 0 { 26 | xMove = movements["right"] 27 | } 28 | *tail = tail.Add(xMove) 29 | 30 | var yMove image.Point 31 | if yDist < 0 { 32 | yMove = movements["down"] 33 | } else if yDist > 0 { 34 | yMove = movements["up"] 35 | } 36 | *tail = tail.Add(yMove) 37 | } 38 | } 39 | 40 | func main() { 41 | file, _ := os.Open("input.txt") 42 | scanner := bufio.NewScanner(file) 43 | moves := []Move{} 44 | for scanner.Scan() { 45 | line := strings.Split(scanner.Text(), " ") 46 | distance, _ := strconv.Atoi(line[1]) 47 | moves = append(moves, Move{direction: line[0], distance: distance}) 48 | } 49 | 50 | movements := map[string]image.Point{ 51 | "up": {0, 1}, "down": {0, -1}, "left": {-1, 0}, "right": {1, 0}, 52 | } 53 | 54 | start := image.Point{0, 0} 55 | knots := [10]image.Point{ 56 | start, start, start, start, start, 57 | start, start, start, start, start, 58 | } 59 | visitedSetOne := map[image.Point]bool{knots[1]: true} 60 | visitedSetTwo := map[image.Point]bool{knots[9]: true} 61 | for _, move := range moves { 62 | for i := 0; i < move.distance; i++ { 63 | switch move.direction { 64 | case "U": 65 | knots[0] = knots[0].Add(movements["up"]) 66 | case "D": 67 | knots[0] = knots[0].Add(movements["down"]) 68 | case "L": 69 | knots[0] = knots[0].Add(movements["left"]) 70 | case "R": 71 | knots[0] = knots[0].Add(movements["right"]) 72 | } 73 | 74 | for i := 1; i < 10; i++ { 75 | moveTail(&knots[i-1], &knots[i], movements) 76 | } 77 | visitedSetOne[knots[1]] = true 78 | visitedSetTwo[knots[9]] = true 79 | } 80 | } 81 | 82 | fmt.Println("Answer One:", len(visitedSetOne)) 83 | 84 | fmt.Println("Answer Two:", len(visitedSetTwo)) 85 | } 86 | -------------------------------------------------------------------------------- /2021/day_05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 5 - Hydrothermal Venture""" 4 | 5 | import re 6 | 7 | 8 | with open('inputs/day_05.txt', 'r') as aoc_input: 9 | lines = [re.findall(r'(\d*),(\d*)', x) for x in aoc_input.readlines()] 10 | lines = [[(int(x), int(y)) for x, y in tup] for tup in lines] 11 | 12 | coords_dict = {} 13 | for line in lines: 14 | current_x, current_y = line[0] 15 | end_x, end_y = line[1] 16 | initial_x = current_x # Needed for column loop entering logic 17 | 18 | if current_y == end_y: 19 | 20 | while True: 21 | coords = (current_x, current_y) 22 | if coords_dict.get(coords, '.') == '.': 23 | coords_dict[coords] = 1 24 | else: 25 | coords_dict[coords] += 1 26 | 27 | if current_x == end_x: 28 | break 29 | 30 | current_x += 1 if current_x < end_x else -1 31 | 32 | if initial_x == end_x: 33 | 34 | while True: 35 | coords = (current_x, current_y) 36 | if coords_dict.get(coords, '.') == '.': 37 | coords_dict[coords] = 1 38 | else: 39 | coords_dict[coords] += 1 40 | 41 | if current_y == end_y: 42 | break 43 | 44 | current_y += 1 if current_y < end_y else -1 45 | 46 | dangerous_zones = sum([1 if x > 1 else 0 for x in coords_dict.values()]) 47 | 48 | # Answer One 49 | print("Number of dangerous zones:", dangerous_zones) 50 | 51 | for line in lines: 52 | current_x, current_y = line[0] 53 | end_x, end_y = line[1] 54 | 55 | # Already been processed in part one 56 | if current_x == end_x or current_y == end_y: 57 | continue 58 | 59 | while True: 60 | coords = (current_x, current_y) 61 | if coords_dict.get(coords, '.') == '.': 62 | coords_dict[coords] = 1 63 | else: 64 | coords_dict[coords] += 1 65 | 66 | if current_x == end_x: 67 | break 68 | 69 | current_x += 1 if current_x < end_x else -1 70 | current_y += 1 if current_y < end_y else -1 71 | 72 | dangerous_zones = sum([1 if x > 1 else 0 for x in coords_dict.values()]) 73 | 74 | # Answer Two 75 | print("Number of dangerous zones including diagonals:", dangerous_zones) 76 | -------------------------------------------------------------------------------- /2017/day_16.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 16 - Permutation Promenade""" 2 | 3 | 4 | def dance(moves, dances): 5 | """ Return final position of programs after the dance moves are done.""" 6 | programs = 'abcdefghijklmnop' 7 | program_list = list(programs) 8 | move_list = moves.strip().split(',') 9 | 10 | completed_dances = 0 11 | seen = [] 12 | cycle_length = 0 13 | 14 | while completed_dances < dances: 15 | for move in move_list: 16 | move = move.strip() 17 | 18 | if move[0] == 's': 19 | to_spin = [] 20 | spin = int(move[1:]) 21 | for program in program_list[-(spin): len(program_list)]: 22 | to_spin.append(program) 23 | for program in program_list[: -spin]: 24 | to_spin.append(program) 25 | program_list = to_spin 26 | 27 | elif move[0] == 'x': 28 | parts = move.split('/') 29 | swap_1 = int(parts[0][1:]) 30 | swap_2 = int(parts[1]) 31 | value_1 = program_list[swap_1] 32 | value_2 = program_list[swap_2] 33 | program_list[swap_1] = value_2 34 | program_list[swap_2] = value_1 35 | 36 | else: 37 | 38 | parts = move.split('/') 39 | pos_1 = program_list.index(parts[0][1:]) 40 | pos_2 = program_list.index(parts[1]) 41 | program_list[pos_1] = parts[1] 42 | program_list[pos_2] = parts[0][1:] 43 | 44 | end_formation = ''.join(program_list) 45 | 46 | if dances == 1: 47 | return end_formation 48 | 49 | if end_formation in seen: 50 | cycle_length = completed_dances + 1 51 | remaining = dances % cycle_length 52 | completed_dances = dances - remaining 53 | seen = [] 54 | 55 | else: 56 | completed_dances += 1 57 | seen.append(programs) 58 | 59 | programs = end_formation 60 | 61 | return programs 62 | 63 | 64 | with open('inputs/day_16.txt') as f: 65 | moves = f.read() 66 | 67 | # Answer One 68 | print('Position after first dance: ' + str(dance(moves, 1))) 69 | 70 | # Answer Two 71 | print('Position after billionth dance: ' 72 | + str(dance(moves, 1000000000))) 73 | -------------------------------------------------------------------------------- /2019/day_10.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 10 - Monitoring Station.""" 2 | 3 | 4 | from math import atan2, degrees, pi 5 | 6 | 7 | with open("inputs/day_10.txt", "r") as f: 8 | space = [list(row) for row in f.read().strip().split('\n')] 9 | 10 | space_dict = {} 11 | y = 0 12 | for row in space: 13 | x = 0 14 | for col in row: 15 | value = space[y][x] 16 | if value == '#': 17 | space_dict[(x, y)] = space[y][x] 18 | x += 1 19 | y += 1 20 | 21 | detection_dict = {} 22 | for coords, value in space_dict.items(): 23 | los = {} 24 | for location, item in space_dict.items(): 25 | if location == coords: 26 | continue 27 | x, y = location 28 | dx = coords[0] - x 29 | dy = coords[1] - y 30 | angle = atan2(dy, dx) 31 | if angle < 0: 32 | angle += 2 * pi 33 | los[location] = degrees(angle) 34 | 35 | detection_dict[coords] = los 36 | 37 | most_detected = None 38 | for asteroid, los_dict in detection_dict.items(): 39 | num_detected = len(set(los_dict.values())) 40 | if not most_detected or num_detected > most_detected[1]: 41 | most_detected = (asteroid, num_detected) 42 | 43 | # Answer One 44 | print("Other asteroids detected from the best location:", most_detected[1]) 45 | 46 | station_coords = most_detected[0] 47 | station = detection_dict[station_coords] 48 | for asteroid, angle in station.items(): 49 | ast_x, ast_y = asteroid 50 | distance = ( 51 | abs(station_coords[0] - ast_x) + abs(station_coords[1] - ast_y) 52 | ) 53 | angle = (angle + 270) % 360 # Rotate so angles start at 12 O'Clock 54 | station[asteroid] = (angle, distance) 55 | 56 | # Sort asteroids by angle, then distance 57 | sorted_asteroids = dict( 58 | sorted(station.items(), key=lambda item: (item[1][0], item[1][1])) 59 | ) 60 | 61 | vaporised_coords = set() 62 | last_angle = None 63 | vaporised = 0 64 | while vaporised != 200: 65 | for asteroid, info in sorted_asteroids.items(): 66 | angle = info[0] 67 | if angle == last_angle or asteroid in vaporised_coords: 68 | continue 69 | last_angle = angle 70 | vaporised_coords.add(asteroid) 71 | vaporised += 1 72 | if vaporised == 200: 73 | break 74 | 75 | # Answer Two 76 | print("Coordinates for 200th vaporised asteroid:", asteroid) 77 | -------------------------------------------------------------------------------- /2016/day_17.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 17 - Two Steps Forward""" 2 | 3 | import hashlib 4 | import collections 5 | 6 | 7 | def make_layout(rows, columns): 8 | """Create a layout of rooms represented by a set of coordinates.""" 9 | locations = set() 10 | for y in range(rows): 11 | for x in range(columns): 12 | locations.add((x, y)) 13 | 14 | return locations 15 | 16 | 17 | def find_open(room, seed, prev): 18 | """Find all open doors in room and return paths not previously traversed.""" 19 | hexed = hashlib.md5('{}'.format(seed).encode('utf-8')).hexdigest()[:5] 20 | opened = [] 21 | open_indicators = 'bcdef' 22 | 23 | if hexed[0] in open_indicators: 24 | opened.append(((room[0], room[1] - 1), seed + 'U')) 25 | 26 | if hexed[1] in open_indicators: 27 | opened.append(((room[0], room[1] + 1), seed + 'D')) 28 | 29 | if hexed[2] in open_indicators: 30 | opened.append(((room[0] - 1, room[1]), seed + 'L')) 31 | 32 | if hexed[3] in open_indicators: 33 | opened.append(((room[0] + 1, room[1]), seed + 'R')) 34 | 35 | return [door for door in opened if door[0] in rooms and door[1] not in prev] 36 | 37 | 38 | def main(passcode, part_two=False): 39 | """Orchestrate DFS returning shortest route or longest if part two.""" 40 | origin = (0, 0) 41 | target = (3, 3) 42 | routes = set() 43 | prev_paths = set() 44 | next_info = find_open(origin, passcode, ()) 45 | 46 | to_process = collections.deque() 47 | while True: 48 | [to_process.append(info) for info in next_info] 49 | if to_process: 50 | info = to_process.pop() 51 | next_node = info[0] 52 | path = info[1] 53 | prev_paths.add(path) 54 | if next_node == target: 55 | routes.add(info[1].strip(passcode)) 56 | next_info = [] 57 | continue 58 | next_info = find_open(next_node, path, prev_paths) 59 | else: 60 | if not part_two: 61 | return min(routes, key=len) 62 | return len(max(routes, key=len)) 63 | 64 | 65 | if __name__ == '__main__': 66 | 67 | rooms = make_layout(4, 4) 68 | 69 | # Answer One 70 | print("Shortest path:", main('dmypynyp')) 71 | 72 | # Answer Two 73 | print("Length of longest path:", main('dmypynyp', part_two=True)) 74 | -------------------------------------------------------------------------------- /2018/day_18.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 18 - Settlers of The North Pole""" 2 | 3 | 4 | with open('inputs/day_18.txt') as f: 5 | rows = [[x for x in list(y.strip())] for y in f.readlines()] 6 | 7 | area = {} 8 | for i, row in enumerate(rows): 9 | for j, acre in enumerate(row): 10 | area[(i, j)] = acre 11 | 12 | # Make minute to minute changes to area until its state repeats 13 | previous = [area] 14 | minutes = 0 15 | while True: 16 | next_minute = {} 17 | for coords, acre in area.items(): 18 | 19 | # Build list of acres next to current acre 20 | next_to = [] 21 | for i in (-1, 0, 1): 22 | for j in (-1, 0, 1): 23 | if i == 0 and j == 0: 24 | continue 25 | next_to.append(area.get((coords[0] + i, coords[1] + j), '')) 26 | 27 | if acre == '.': 28 | if next_to.count('|') >= 3: 29 | next_minute[coords] = '|' 30 | else: 31 | next_minute[coords] = '.' 32 | 33 | elif acre == '|': 34 | if next_to.count('#') >= 3: 35 | next_minute[coords] = '#' 36 | else: 37 | next_minute[coords] = '|' 38 | 39 | elif acre == '#': 40 | if next_to.count('#') > 0 and next_to.count('|') > 0: 41 | next_minute[coords] = '#' 42 | else: 43 | next_minute[coords] = '.' 44 | 45 | area = next_minute 46 | minutes += 1 47 | 48 | # Answer One 49 | if minutes == 10: 50 | wooded, lumber = 0, 0 51 | for acre in area.values(): 52 | if acre == '|': 53 | wooded += 1 54 | elif acre == '#': 55 | lumber += 1 56 | 57 | print("After 10 minutes:", wooded * lumber) 58 | 59 | if area in previous: 60 | break 61 | previous.append(area) 62 | 63 | # Find where period starts and its length to find state at 1000000000 64 | repeat_index = previous.index(area) 65 | period_length = minutes - repeat_index 66 | after_repeat = 1000000000 % period_length 67 | answer = previous[repeat_index + after_repeat] 68 | 69 | # Answer Two 70 | wooded, lumber = 0, 0 71 | for acre in answer.values(): 72 | if acre == '|': 73 | wooded += 1 74 | elif acre == '#': 75 | lumber += 1 76 | 77 | # Answer Two 78 | print("After many minutes:", wooded * lumber) 79 | -------------------------------------------------------------------------------- /2021/day_04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 4 - Giant Squid""" 4 | 5 | 6 | import re 7 | 8 | 9 | def mark_cards(cards, number): 10 | """Go through cards list marking off number whenever it appears.""" 11 | for card in cards: 12 | for row_index, row in enumerate(card): 13 | card[row_index] = [x if x != number else None for x in row] 14 | 15 | return cards 16 | 17 | 18 | def check_for_winner(cards): 19 | """Return indices of winning cards i.e. has a completed row or column.""" 20 | winners = [] 21 | for card_num, card in enumerate(cards): 22 | row_winner = False 23 | for row in card: 24 | if len(set(row)) == 1: 25 | winners.append(card_num) 26 | row_winner = True 27 | break 28 | 29 | if row_winner: 30 | continue 31 | 32 | for col_index in range(len(card[0])): 33 | column = [] 34 | for row in card: 35 | column.append(row[col_index]) 36 | 37 | if len(set(column)) == 1: 38 | winners.append(card_num) 39 | break 40 | 41 | return winners 42 | 43 | 44 | with open('inputs/day_04.txt', 'r') as aoc_input: 45 | sections = aoc_input.read().strip().split('\n\n') 46 | 47 | balls = [int(x) for x in sections[0].split(',')] 48 | 49 | cards = [] 50 | for part in sections[1:]: 51 | cards.append([]) 52 | for row in part.split('\n'): 53 | cards[-1].append([int(x) for x in re.findall(r'\d\d?', row)]) 54 | 55 | completed_cards = [] 56 | for ball in balls: 57 | cards = mark_cards(cards, ball) 58 | 59 | winning_indices = check_for_winner(cards) 60 | if winning_indices: 61 | for index in reversed(winning_indices): 62 | completed_cards.append((cards[index], ball)) 63 | del cards[index] 64 | 65 | if len(cards) == 0: 66 | break 67 | 68 | winning_card, winning_ball = completed_cards[0] 69 | winning_sum = sum([sum([x for x in row if x != None]) for row in winning_card]) 70 | 71 | # Answer One 72 | print("Final score of winning card:", winning_sum * winning_ball) 73 | 74 | losing_card, losing_ball = completed_cards[-1] 75 | losing_sum = sum([sum([x for x in row if x != None]) for row in losing_card]) 76 | 77 | # Answer Two 78 | print("Final score of losing card:", losing_sum * losing_ball) 79 | -------------------------------------------------------------------------------- /2021/day_15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 15 - Chiton""" 4 | 5 | 6 | def dijkstra(map_dict, end_coords): 7 | unvisited = set(map_dict.keys()) 8 | node_dict = {(0, 0): 0} 9 | current_node = (0, 0) 10 | while node_dict: 11 | x, y = current_node 12 | risk = node_dict[current_node] 13 | 14 | adjacent = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)] 15 | for next_to in adjacent: 16 | if next_to not in unvisited: 17 | continue 18 | 19 | current_risk = node_dict.get(next_to, None) 20 | new_risk = risk + map_dict[next_to] 21 | if not current_risk or new_risk < current_risk: 22 | node_dict[next_to] = new_risk 23 | 24 | unvisited.remove(current_node) 25 | del node_dict[current_node] 26 | 27 | lowest_risk = min(node_dict.values()) 28 | for coords, risk in node_dict.items(): 29 | if risk == lowest_risk: 30 | current_node = coords 31 | break 32 | 33 | if current_node == end_coords: 34 | return node_dict[end_coords] 35 | 36 | return None 37 | 38 | 39 | with open('inputs/day_15.txt', 'r') as aoc_input: 40 | lines = [line.strip() for line in aoc_input.readlines()] 41 | 42 | map_dict = {} 43 | for y, row in enumerate(lines): 44 | for x, risk_level in enumerate(row): 45 | map_dict[(x, y)] = int(risk_level) 46 | 47 | end_coords = (len(lines) - 1, len(lines[0]) - 1) 48 | 49 | # Answer One 50 | print("Lowest total risk of any path:", dijkstra(map_dict, end_coords)) 51 | 52 | # Build full map 53 | full_map = map_dict.copy() 54 | for coords, risk in map_dict.items(): 55 | x, y = coords 56 | for i in range(1, 5): 57 | new_risk = (risk + i) % 9 58 | new_risk = 9 if new_risk == 0 else new_risk 59 | 60 | new_x = x + ((end_coords[0] + 1) * i) 61 | full_map[(new_x, y)] = new_risk 62 | 63 | map_dict = full_map.copy() 64 | for coords, risk in map_dict.items(): 65 | x, y = coords 66 | for i in range(1, 5): 67 | new_risk = (risk + i) % 9 68 | new_risk = 9 if new_risk == 0 else new_risk 69 | 70 | new_y = y + ((end_coords[1] + 1) * i) 71 | full_map[(x, new_y)] = new_risk 72 | 73 | new_end_coords = ((end_coords[0] + 1) * 5 - 1, (end_coords[1] + 1) * 5 - 1) 74 | 75 | # Answer Two 76 | print("Lowest total risk on full map:", dijkstra(full_map, new_end_coords)) 77 | -------------------------------------------------------------------------------- /2020/day_12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 12 - Rain Risk.""" 4 | 5 | 6 | with open ('inputs/day_12.txt', 'r') as f: 7 | instructions = [row.strip() for row in f.readlines()] 8 | 9 | coords = [0, 0] 10 | directions = {0: 'E', 1: 'S', 2: 'W', 3: 'N'} 11 | current_direction = 0 12 | for instruction in instructions: 13 | action = instruction[0] 14 | value = int(instruction[1:]) 15 | 16 | if action == 'N': 17 | coords[0] += value 18 | elif action == 'S': 19 | coords[0] -= value 20 | elif action == 'E': 21 | coords[1] += value 22 | elif action == 'W': 23 | coords[1] -= value 24 | elif action == 'L': 25 | current_direction = (current_direction - (value / 90)) % 4 26 | elif action == 'R': 27 | current_direction = (current_direction + (value / 90)) % 4 28 | elif action == 'F': 29 | facing = directions[current_direction] 30 | if facing == 'N': 31 | coords[0] += value 32 | elif facing == 'S': 33 | coords[0] -= value 34 | elif facing == 'E': 35 | coords[1] += value 36 | elif facing == 'W': 37 | coords[1] -= value 38 | 39 | # Answer One 40 | print("Manhattan distance from starting point:", 41 | sum([abs(coord) for coord in coords])) 42 | 43 | coords = [0, 0] 44 | waypoint = [1, 10] 45 | for instruction in instructions: 46 | action = instruction[0] 47 | value = int(instruction[1:]) 48 | 49 | if action == 'N': 50 | waypoint[0] += value 51 | elif action == 'S': 52 | waypoint[0] -= value 53 | elif action == 'E': 54 | waypoint[1] += value 55 | elif action == 'W': 56 | waypoint[1] -= value 57 | elif action == 'L': 58 | for n in range(value // 90): 59 | new_waypoint = [None, None] 60 | new_waypoint[1] = -waypoint[0] 61 | new_waypoint[0] = waypoint[1] 62 | waypoint = new_waypoint 63 | elif action == 'R': 64 | for n in range(value // 90): 65 | new_waypoint = [None, None] 66 | new_waypoint[1] = waypoint[0] 67 | new_waypoint[0] = -waypoint[1] 68 | waypoint = new_waypoint 69 | elif action == 'F': 70 | coords = [coords[0] + waypoint[0] * value, 71 | coords[1] + waypoint[1] * value] 72 | 73 | # Answer Two 74 | print("Actual Manhattan distance from starting point:", 75 | sum([abs(coord) for coord in coords])) 76 | -------------------------------------------------------------------------------- /2019/day_22.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 22 - Slam Shuffle.""" 2 | 3 | 4 | def deal_new_stack(cards): 5 | """Return reversed cards.""" 6 | cards.reverse() 7 | return cards 8 | 9 | 10 | def cut(cards, cut_size): 11 | """Perform a cut on the cards.""" 12 | return cards[cut_size:] + cards[:cut_size] 13 | 14 | 15 | def deal_with_increment(cards, increment): 16 | """Deal cards into new order based upon an increment.""" 17 | num_cards = len(cards) 18 | pos = 0 19 | new_cards = cards.copy() 20 | for card in cards: 21 | new_cards[pos] = card 22 | pos = (pos + increment) % num_cards 23 | return new_cards 24 | 25 | 26 | 27 | with open('inputs/day_22.txt') as f: 28 | shuffle = [line.strip() for line in f.readlines()] 29 | 30 | deck = list(range(10007)) 31 | for instruction in shuffle: 32 | if 'new' in instruction: 33 | deck = deal_new_stack(deck) 34 | elif 'cut' in instruction: 35 | deck = cut(deck, int(instruction.split()[-1])) 36 | elif 'increment' in instruction: 37 | deck = deal_with_increment(deck, int(instruction.split()[-1])) 38 | 39 | # Answer One 40 | print("Index of card 2019 post-shuffling:", deck.index(2019)) 41 | 42 | 43 | """ 44 | Reduce each shuffle type to its effect on the offset (which card appears first) 45 | and the increment (the difference between adjacent numbers) as all deck states 46 | can be encoded through those two paramneters. 47 | Run through the shuffle instructions once, tracking the changes to the offset 48 | and increment, and then iterate these tracked changes the required amount. 49 | The iteration of increment is simply exponential whereas the iteration of the 50 | offset is largely a geometric series. 51 | """ 52 | 53 | num_cards = 119315717514047 54 | iterations = 101741582076661 55 | increment = 1 56 | offset = 0 57 | for curr_shuffle in shuffle: 58 | if 'new' in curr_shuffle: 59 | increment *= -1 60 | offset += increment 61 | elif 'cut' in curr_shuffle: 62 | offset += int(curr_shuffle.split()[-1]) * increment 63 | else: 64 | increment *= pow(int(curr_shuffle.split()[-1]), -1, num_cards) 65 | 66 | total_increment = pow(increment, iterations, num_cards) 67 | total_offset = offset * (1 - pow(increment, iterations, num_cards)) 68 | total_offset *= pow(1 - increment, -1, num_cards) 69 | 70 | value_2020 = (2020 * total_increment + total_offset) % num_cards 71 | 72 | # Answer Two 73 | print("Number on card at position 2020:", value_2020) 74 | -------------------------------------------------------------------------------- /2020/day_08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 08 - Handheld Halting.""" 4 | 5 | 6 | def run_boot_code(instructions: list) -> tuple: 7 | """Run boot code in instructions list. 8 | 9 | Args: 10 | instructions: List of boot code instruction strings in the format 11 | "operation argument". 12 | 13 | Returns: 14 | Tuple of (accumulator, boolean). The boolean represents whether 15 | the code ran to completion - indicatd by trying to access an 16 | index beyond the instruction list - or not. The accumulator is the 17 | value of the accumulator at completion or, in the case of an 18 | infinite loop, prior to entering the loop. 19 | """ 20 | accumulator = 0 21 | visited_indicies = set([0]) 22 | current_index = 0 23 | while True: 24 | try: 25 | operation, argument = instructions[current_index].split() 26 | except IndexError: 27 | # Indicates program ran to completion 28 | return (accumulator, True) 29 | 30 | argument = int(argument) 31 | if operation == "acc": 32 | current_index += 1 33 | accumulator += argument 34 | elif operation == 'jmp': 35 | current_index += argument 36 | elif operation == 'nop': 37 | current_index += 1 38 | 39 | if current_index in visited_indicies: 40 | return (accumulator, False) 41 | visited_indicies.add(current_index) 42 | 43 | 44 | with open ('inputs/day_08.txt', 'r') as boot_code: 45 | instructions = [instruction for instruction in boot_code.readlines()] 46 | 47 | # Answer One 48 | print("Value in the accumulator before infinite loop:", 49 | run_boot_code(instructions)[0]) 50 | 51 | current_index = 0 52 | while True: 53 | operation, argument = instructions[current_index].split() 54 | if operation in ('jmp', 'nop'): 55 | mod_instructions = instructions.copy() 56 | if operation == 'jmp': 57 | mod_instructions[current_index] = "{} {}".format('nop', argument) 58 | elif operation == 'nop': 59 | mod_instructions[current_index] = "{} {}".format('jmp', argument) 60 | 61 | accumulator, program_fixed = run_boot_code(mod_instructions) 62 | if program_fixed: 63 | break 64 | 65 | current_index += 1 66 | 67 | # Answer Two 68 | print("Value of the accumulator after the program terminates:", accumulator) 69 | -------------------------------------------------------------------------------- /2016/day_21.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 21 - Scrambled Letters and Hash""" 2 | 3 | from itertools import permutations 4 | 5 | 6 | def scramble(password, operations): 7 | """Scramble a string via a list of operations.""" 8 | pass_list = [char for char in password] 9 | for operation in operations: 10 | parse = operation.split(' ') 11 | if parse[0] == 'swap': 12 | if parse[1] == 'position': 13 | temp = pass_list[int(parse[2])] 14 | pass_list[int(parse[2])] = pass_list[int(parse[-1])] 15 | pass_list[int(parse[-1])] = temp 16 | else: 17 | x_pos = pass_list.index(parse[2]) 18 | y_pos = pass_list.index(parse[-1]) 19 | temp = pass_list[x_pos] 20 | pass_list[x_pos] = pass_list[y_pos] 21 | pass_list[y_pos] = temp 22 | 23 | if parse[0] == 'rotate': 24 | if parse[1] == 'left': 25 | for _ in range(int(parse[2])): 26 | pass_list.append(pass_list.pop(0)) 27 | 28 | elif parse[1] == 'right': 29 | for _ in range(int(parse[2])): 30 | pass_list.insert(0, pass_list.pop(-1)) 31 | 32 | elif parse[1] == 'based': 33 | index = pass_list.index(parse[-1]) 34 | for _ in range(index + 1): 35 | pass_list.insert(0, pass_list.pop(-1)) 36 | if index >= 4: 37 | pass_list.insert(0, pass_list.pop(-1)) 38 | 39 | if parse[0] == 'reverse': 40 | start = int(parse[2]) 41 | end = int(parse[-1]) 42 | pass_str = ''.join(pass_list) 43 | if start == 0: 44 | new_str = pass_str[end::-1] + pass_str[end+1:] 45 | else: 46 | new_str = pass_str[0:start] + pass_str[end:start-1:-1] + pass_str[end + 1:] 47 | 48 | pass_list = [char for char in new_str] 49 | 50 | if parse[0] == 'move': 51 | pass_list.insert(int(parse[-1]), pass_list.pop(int(parse[2]))) 52 | 53 | return ''.join(pass_list) 54 | 55 | 56 | with open('inputs/day_21.txt') as f: 57 | lines = [line.strip() for line in f.readlines()] 58 | 59 | # Answer One 60 | print("Scrambled password:", scramble('abcdefgh', lines)) 61 | 62 | perms = permutations('abcdefgh') 63 | for perm in perms: 64 | if scramble(perm, lines) == 'fbgdceah': 65 | # Answer Two 66 | print("Unscrambled password:", ''.join(perm)) 67 | break 68 | -------------------------------------------------------------------------------- /2020/day_24.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 24 - Lobby Layout.""" 4 | 5 | 6 | with open('inputs/day_24.txt') as f: 7 | instructions = [line.strip() for line in f.readlines()] 8 | 9 | tiles = {} 10 | directions = { 11 | 'e': (-1, 1, 0), 'se': (-1, 0, 1), 'sw': (0, -1, 1), 12 | 'w': (1, -1, 0), 'nw': (1, 0, -1), 'ne': (0, 1, -1) 13 | } 14 | for instruction in instructions: 15 | current_coords = (0, 0, 0) 16 | index = 0 17 | while index < len(instruction): 18 | y, x, z = current_coords 19 | if instruction[index] not in directions: 20 | move = "{}{}".format(instruction[index], instruction[index + 1]) 21 | index += 1 22 | else: 23 | move = instruction[index] 24 | dy, dx, dz = directions[move] 25 | current_coords = (y + dy, x + dx, z + dz) 26 | index += 1 27 | 28 | current_colour = tiles.get(current_coords, 0) 29 | if current_colour == 0: 30 | tiles[current_coords] = 1 31 | else: 32 | del tiles[current_coords] 33 | 34 | # Answer One 35 | print("Number of black tiles:", len(tiles)) 36 | 37 | for _ in range(100): 38 | new_tiles = {} 39 | checked = set() 40 | for coords in tiles: 41 | y, x, z = coords 42 | black_adjacent = 0 43 | for next_to in directions.values(): 44 | next_to_coords = (next_to[0] + y, next_to[1] + x, next_to[2] + z) 45 | if next_to_coords in tiles: 46 | black_adjacent += 1 47 | 48 | elif next_to_coords in checked: 49 | pass 50 | 51 | else: 52 | double_black_adjacent = 0 53 | for double_next_to in directions.values(): 54 | double_next_coords = ( 55 | double_next_to[0] + next_to_coords[0], 56 | double_next_to[1] + next_to_coords[1], 57 | double_next_to[2] + next_to_coords[2] 58 | ) 59 | 60 | if double_next_coords in tiles: 61 | double_black_adjacent += 1 62 | 63 | if double_black_adjacent == 2: 64 | new_tiles[next_to_coords] = 1 65 | 66 | checked.add(next_to_coords) 67 | 68 | if black_adjacent in (1, 2): 69 | new_tiles[coords] = 1 70 | 71 | checked.add(coords) 72 | 73 | tiles = new_tiles 74 | 75 | # Answer Two 76 | print("Number of black tiles after 100 days:", len(tiles)) 77 | -------------------------------------------------------------------------------- /2018/day_13.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 13 - Cart Madness.""" 2 | 3 | 4 | with open('inputs/day_13.txt') as f: 5 | track = [[x for x in list(line.rstrip())] for line in f.readlines()] 6 | 7 | # Find carts, set next_turn to 0 (left) and replace track beneath it 8 | carts = [] 9 | for y, line in enumerate(track): 10 | for x, piece in enumerate(line): 11 | if piece in ('^', 'v', '>', '<'): 12 | carts.append(((y, x), piece, 0)) 13 | 14 | if piece in ('^', 'v'): 15 | track[y][x] = '|' 16 | else: 17 | track[y][x] = '-' 18 | 19 | # Dictionaries for direction conversions 20 | cart_moves = {'^': (-1, 0), 'v': (1, 0), '>': (0, 1), '<': (0, -1)} 21 | left_turn = {'^': '<', 'v': '>', '>': '^', '<': 'v'} 22 | right_turn = {'^': '>', 'v': '<', '>': 'v', '<': '^'} 23 | back = {'^': '<', 'v': '>', '<': '^', '>': 'v'} 24 | forward = {'^': '>', 'v': '<', '<': 'v', '>': '^'} 25 | 26 | # Until one cart remains, sort by row,col then move in order removing crashed 27 | part_one = True 28 | while len(carts) > 1: 29 | carts = sorted(carts, key=lambda element: (element[0][0], element[0][1])) 30 | new_carts = [] 31 | while carts: 32 | cart = carts.pop(0) 33 | coords, direction, turn = cart 34 | y, x = coords 35 | change_y, change_x = cart_moves[direction] 36 | y, x = y + change_y, x + change_x 37 | 38 | piece = track[y][x] 39 | if piece == '+': 40 | if turn % 3 == 0: 41 | direction = left_turn[direction] 42 | elif turn % 3 == 2: 43 | direction = right_turn[direction] 44 | turn += 1 45 | 46 | elif piece == '\\': 47 | direction = back[direction] 48 | elif piece =='/': 49 | direction = forward[direction] 50 | 51 | cart_check = [x[0] for x in carts] + [x[0] for x in new_carts] 52 | if (y, x) in cart_check: 53 | if part_one: 54 | # Answer One 55 | print("First crash: {},{}".format(x, y)) 56 | part_one = False 57 | 58 | # Find crashed cart and remove it, skip adding this one to new 59 | carts = [cart for cart in carts if cart[0] != (y, x)] 60 | new_carts = [cart for cart in new_carts if cart[0] != (y, x)] 61 | continue 62 | 63 | new_carts.append(((y, x), direction, turn)) 64 | 65 | carts = new_carts 66 | 67 | # Answer Two 68 | final_cart = carts[0] 69 | print("Final cart: {},{}".format(final_cart[0][1], final_cart[0][0])) 70 | -------------------------------------------------------------------------------- /2021/day_12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2021 Day 12 - Passage Pathing""" 4 | 5 | 6 | with open('inputs/day_12.txt', 'r') as aoc_input: 7 | lines = [line.strip().split('-') for line in aoc_input.readlines()] 8 | 9 | connections = {} 10 | for line in lines: 11 | from_cave, to_cave = line 12 | if from_cave not in connections.keys(): 13 | connections[from_cave] = set() 14 | if to_cave not in connections.keys(): 15 | connections[to_cave] = set() 16 | 17 | connections[from_cave].add(to_cave) 18 | connections[to_cave].add(from_cave) 19 | 20 | paths = set() 21 | current_paths = [] 22 | for to_cave in connections['start']: 23 | current_paths.append(('start', to_cave)) 24 | 25 | while current_paths: 26 | path = current_paths.pop() 27 | current_cave = path[-1] 28 | for connected_cave in connections[current_cave]: 29 | 30 | # Can't revisit small caves 31 | if connected_cave in path and connected_cave.lower() == connected_cave: 32 | continue 33 | 34 | new_path = path + (connected_cave,) 35 | 36 | # Check if at end and unique path 37 | if connected_cave == 'end' and new_path not in paths: 38 | paths.add(new_path) 39 | continue 40 | 41 | current_paths.append(new_path) 42 | 43 | # Answer One 44 | print("Number of paths through cave system:", len(paths)) 45 | 46 | paths = set() 47 | current_paths = [] 48 | for to_cave in connections['start']: 49 | current_paths.append((('start', to_cave), False)) 50 | 51 | while current_paths: 52 | path, revisited = current_paths.pop() 53 | current_cave = path[-1] 54 | for connected_cave in connections[current_cave]: 55 | new_path_revisited = revisited 56 | 57 | # Can't go back to start 58 | if connected_cave == 'start': 59 | continue 60 | 61 | # Can't revisit small caves more than once for one of them 62 | if connected_cave in path: 63 | if connected_cave.lower() == connected_cave: 64 | if new_path_revisited: 65 | continue 66 | else: 67 | new_path_revisited = True 68 | 69 | new_path = path + (connected_cave,) 70 | 71 | # Check if at end and unique path 72 | if connected_cave == 'end' and new_path not in paths: 73 | paths.add(new_path) 74 | continue 75 | 76 | current_paths.append(((new_path), new_path_revisited)) 77 | 78 | # Answer Two 79 | print("Number of paths through cave system:", len(paths)) 80 | -------------------------------------------------------------------------------- /2019/day_03.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 03 - Crossed Wires.""" 2 | 3 | 4 | 5 | def wire_path(wire): 6 | """Plot wire path and return coords set and coords: No. of steps dict.""" 7 | wired = set() 8 | steps = {} 9 | num_steps = 1 10 | wire_end = [0, 0] 11 | for path in wire: 12 | direction = path[0] 13 | distance = int(path[1:]) 14 | 15 | if direction == 'R': 16 | for moved in range(1, distance + 1): 17 | wired.add((moved + wire_end[0], wire_end[1])) 18 | steps[(moved + wire_end[0], wire_end[1])] = num_steps 19 | num_steps += 1 20 | wire_end[0] = distance + wire_end[0] 21 | 22 | 23 | elif direction == 'L': 24 | for moved in range(1, distance + 1): 25 | wired.add((wire_end[0] - moved, wire_end[1])) 26 | steps[(wire_end[0] - moved, wire_end[1])] = num_steps 27 | num_steps += 1 28 | wire_end[0] = wire_end[0] - distance 29 | 30 | 31 | elif direction == 'U': 32 | for moved in range(1, distance + 1): 33 | wired.add((wire_end[0], moved + wire_end[1])) 34 | steps[(wire_end[0], moved + wire_end[1])] = num_steps 35 | num_steps += 1 36 | wire_end[1] = distance + wire_end[1] 37 | 38 | 39 | elif direction == 'D': 40 | for moved in range(1, distance + 1): 41 | wired.add((wire_end[0], wire_end[1] - moved)) 42 | steps[(wire_end[0], wire_end[1] - moved)] = num_steps 43 | num_steps += 1 44 | wire_end[1] = wire_end[1] - distance 45 | 46 | return wired, steps 47 | 48 | 49 | with open('inputs/day_03.txt') as f: 50 | wire_paths = [path.split(',') for path in f.read().split()] 51 | 52 | wire_path_1, num_steps_1 = wire_path(wire_paths[0]) 53 | wire_path_2, num_steps_2 = wire_path(wire_paths[1]) 54 | intersections = wire_path_1 & wire_path_2 55 | 56 | nearest = None 57 | for crossed in intersections: 58 | manhat_distance = abs(crossed[0]) + abs(crossed[1]) 59 | if not nearest or manhat_distance < nearest: 60 | nearest = manhat_distance 61 | 62 | # Answer One 63 | print("Manhattan distance from central port to nearest intersection:", nearest) 64 | 65 | fewest_steps = None 66 | for crossed in intersections: 67 | combined_steps = num_steps_1[crossed] + num_steps_2[crossed] 68 | if not fewest_steps or combined_steps < fewest_steps: 69 | fewest_steps = combined_steps 70 | 71 | # Answer Two 72 | print("Fewest combined steps to reach an intersection:", fewest_steps) 73 | 74 | -------------------------------------------------------------------------------- /2022/day_03/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | "unicode" 9 | ) 10 | 11 | type Rucksack struct { 12 | firstCompartment string 13 | secondCompartment string 14 | } 15 | 16 | func runeListToPrioritySum(runeList []rune) int { 17 | prioritySum := 0 18 | for _, char := range runeList { 19 | prioritySum += int(char) 20 | if unicode.IsUpper(char) { 21 | prioritySum -= 38 22 | } else { 23 | prioritySum -= 96 24 | } 25 | } 26 | 27 | return prioritySum 28 | } 29 | 30 | func partOne(rucksacks []Rucksack) int { 31 | runeList := []rune{} 32 | for _, rucksack := range rucksacks { 33 | oneSet := map[rune]bool{} 34 | for _, char := range rucksack.firstCompartment { 35 | oneSet[char] = true 36 | } 37 | for _, char := range rucksack.secondCompartment { 38 | if oneSet[char] { 39 | runeList = append(runeList, char) 40 | break 41 | } 42 | } 43 | } 44 | 45 | return runeListToPrioritySum(runeList) 46 | } 47 | 48 | func partTwo(rucksacks []Rucksack) int { 49 | runeList := []rune{} 50 | i := 0 51 | for i < len(rucksacks) { 52 | elfOne := rucksacks[i] 53 | elfTwo := rucksacks[i+1] 54 | elfThree := rucksacks[i+2] 55 | 56 | oneSet := map[rune]bool{} 57 | for _, char := range elfOne.firstCompartment + elfOne.secondCompartment { 58 | oneSet[char] = true 59 | } 60 | 61 | oneTwoIntersection := map[rune]bool{} 62 | for _, char := range elfTwo.firstCompartment + elfTwo.secondCompartment { 63 | if oneSet[char] { 64 | oneTwoIntersection[char] = true 65 | } 66 | } 67 | 68 | for _, char := range elfThree.firstCompartment + elfThree.secondCompartment { 69 | if oneTwoIntersection[char] { 70 | runeList = append(runeList, char) 71 | break 72 | } 73 | } 74 | 75 | i += 3 76 | } 77 | 78 | return runeListToPrioritySum(runeList) 79 | } 80 | 81 | func main() { 82 | file, err := os.Open("input.txt") 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | defer file.Close() 87 | 88 | scanner := bufio.NewScanner(file) 89 | rucksacks := []Rucksack{} 90 | for scanner.Scan() { 91 | line := scanner.Text() 92 | halfway := len(line) / 2 93 | rucksack := Rucksack{firstCompartment: line[:halfway], secondCompartment: line[halfway:]} 94 | rucksacks = append(rucksacks, rucksack) 95 | } 96 | if err := scanner.Err(); err != nil { 97 | log.Fatal(err) 98 | } 99 | 100 | answerOne := partOne(rucksacks) 101 | fmt.Printf("Answer One: %d\n", answerOne) 102 | 103 | answerTwo := partTwo(rucksacks) 104 | fmt.Printf("Answer Two: %d\n", answerTwo) 105 | } 106 | -------------------------------------------------------------------------------- /2018/day_07.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 7 - The Sum of Its Parts""" 2 | 3 | 4 | def order_steps(steps): 5 | """Return order steps must be taken given their requirements.""" 6 | num_steps = len(steps) 7 | order = '' 8 | while num_steps: 9 | ready_steps = [] 10 | for step, requirements in steps.items(): 11 | if step in order: 12 | continue 13 | 14 | ready = True 15 | for required in requirements: 16 | if required not in order: 17 | ready = False 18 | break 19 | 20 | if ready: 21 | ready_steps.append(step) 22 | 23 | ready_steps.sort() 24 | order += ready_steps[0] 25 | num_steps -= 1 26 | 27 | return order 28 | 29 | 30 | def build_time(steps): 31 | """Return build time if steps take 60 + letter value secs to complete.""" 32 | step_times = dict(zip(list('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), range(61, 87))) 33 | build_time = 0 34 | build_queue = [] 35 | completed = set() 36 | num_steps = len(steps) 37 | while num_steps: 38 | ready = [] 39 | for step, requirements in steps.items(): 40 | if step in completed or step in [x[0] for x in build_queue]: 41 | continue 42 | 43 | step_ready = True 44 | for required in requirements: 45 | if required not in completed: 46 | step_ready = False 47 | break 48 | 49 | if step_ready: 50 | ready.append([step, step_times[step]]) 51 | 52 | ready.sort() 53 | build_queue.extend(ready) 54 | 55 | done = build_queue.pop(0) 56 | completed.add(done[0]) 57 | build_time += done[1] 58 | num_steps -= 1 59 | # Other 4 workers make progress equal to done time on their tasks 60 | for in_progress in build_queue[:4]: 61 | in_progress[1] = in_progress[1] - done[1] 62 | 63 | return build_time 64 | 65 | 66 | if __name__ == '__main__': 67 | 68 | with open('inputs/day_07.txt') as f: 69 | lines = f.readlines() 70 | 71 | # Make dict associating each step with its requirements 72 | steps = {} 73 | for line in lines: 74 | parse = line.split() 75 | if parse[7] not in steps: 76 | steps[parse[7]] = set() 77 | steps[parse[7]].add(parse[1]) 78 | 79 | if parse[1] not in steps: 80 | steps[parse[1]] = set() 81 | 82 | # Answer One 83 | print("Order of steps:", order_steps(steps)) 84 | 85 | # Answer Two 86 | print("Time to build:", build_time(steps)) 87 | -------------------------------------------------------------------------------- /2019/day_05.py: -------------------------------------------------------------------------------- 1 | """Advent of Code 2019 Day 5 - Sunny with a Chance of Asteroids.""" 2 | 3 | 4 | def run_intcode(memory, input_value): 5 | """Run intcode program on memory set with given input value.""" 6 | diagnostic_code = 0 7 | pointer = 0 8 | while True: 9 | opcode = memory[pointer] 10 | 11 | if opcode > 100: 12 | opcode, modes = parse_instructions_opcode(opcode) 13 | else: 14 | modes = [0, 0, 0] 15 | 16 | if opcode == 99: 17 | return diagnostic_code 18 | 19 | param_1 = pointer + 1 if modes[0] else memory[pointer + 1] 20 | param_2 = pointer + 2 if modes[1] else memory[pointer + 2] 21 | param_3 = memory[pointer + 3] 22 | 23 | if opcode == 1: 24 | memory[param_3] = memory[param_1] + memory[param_2] 25 | pointer += 4 26 | 27 | elif opcode == 2: 28 | memory[param_3] = memory[param_1] * memory[param_2] 29 | pointer += 4 30 | 31 | elif opcode == 3: 32 | memory[param_1] = input_value 33 | pointer += 2 34 | 35 | elif opcode == 4: 36 | diagnostic_code = memory[param_1] 37 | #print(diagnostic_code) 38 | pointer += 2 39 | 40 | elif opcode == 5: 41 | if memory[param_1] == 0: 42 | pointer += 3 43 | else: 44 | pointer = memory[param_2] 45 | 46 | elif opcode == 6: 47 | if memory[param_1] != 0: 48 | pointer += 3 49 | else: 50 | pointer = memory[param_2] 51 | 52 | elif opcode == 7: 53 | if memory[param_1] < memory[param_2]: 54 | memory[param_3] = 1 55 | else: 56 | memory[param_3] = 0 57 | pointer += 4 58 | 59 | elif opcode == 8: 60 | if memory[param_1] == memory[param_2]: 61 | memory[param_3] = 1 62 | else: 63 | memory[param_3] = 0 64 | pointer += 4 65 | 66 | 67 | def parse_instructions_opcode(value): 68 | """Return opcode and mode parsed from instruction value.""" 69 | str_value = str(value) 70 | opcode = int(str_value[-2:]) 71 | modes = [int(x) for x in list(str_value)[:-2]] 72 | while len(modes) != 3: 73 | modes.insert(0, 0) 74 | return (opcode, list(reversed(modes))) 75 | 76 | 77 | with open('inputs/day_05.txt', 'r') as f: 78 | program = [int(x) for x in f.read().split(',')] 79 | 80 | # Answer One 81 | print("Diagnostic Code for ID 1:", run_intcode(list(program), 1)) 82 | 83 | # Answer Two 84 | print("Diagnostic Code for ID 5:", run_intcode(list(program), 5)) 85 | -------------------------------------------------------------------------------- /2018/day_06.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 6 - Chronal Coordinates""" 2 | 3 | import collections 4 | 5 | 6 | def largest_bounded(max_side, area_origins): 7 | """Return size of the largest bounded area.""" 8 | infinite_areas = set() 9 | area_sizes = collections.defaultdict(int) 10 | for row in range(max_side+1): 11 | for col in range(max_side+1): 12 | closest = closest_origin((row, col), area_origins) 13 | if closest: 14 | area_sizes[closest] += 1 15 | 16 | if row in (0, max_side) or col in (0, max_side): 17 | infinite_areas.add(closest) 18 | 19 | for infinite_area in infinite_areas: 20 | del area_sizes[infinite_area] 21 | 22 | return max(area_sizes.values()) 23 | 24 | 25 | def closest_origin(coord, area_origins): 26 | """Return closest area origin to coord based on Manhattan Distance.""" 27 | closest, min_distance = None, 10000 28 | for origin in area_origins: 29 | distance = abs(coord[0] - origin[0]) + abs(coord[1] - origin[1]) 30 | if distance < min_distance: 31 | closest, min_distance = area_origins[origin], distance 32 | shared = False 33 | 34 | elif distance == min_distance: 35 | shared = True 36 | 37 | return False if shared else closest 38 | 39 | 40 | def all_close(max_side, area_origins, limit): 41 | """Return region size where distance sum of point from origins < limit.""" 42 | close_region_size = 0 43 | for row in range(max_side+1): 44 | for col in range(max_side+1): 45 | distance = limit 46 | for origin in area_origins: 47 | distance -= abs(row - origin[0]) + abs(col - origin[1]) 48 | if distance <= 0: 49 | distance = False 50 | break 51 | 52 | if distance: 53 | close_region_size += 1 54 | 55 | return close_region_size 56 | 57 | 58 | if __name__ == '__main__': 59 | 60 | with open('inputs/day_06.txt') as f: 61 | coordinates = f.readlines() 62 | 63 | # Build origins dict and find required side length to capture all origins 64 | area_origins = {} 65 | max_side = 0 66 | for num, coord in enumerate(coordinates): 67 | col, row = [int(x) for x in coord.split(',')] 68 | furthest = max(col, row) 69 | if furthest > max_side: 70 | max_side = furthest 71 | 72 | area_origins[(row, col)] = str(num) 73 | 74 | # Answer One 75 | print("Size of largest area:", largest_bounded(max_side, area_origins)) 76 | 77 | # Answer Two 78 | print("Size of close region:", all_close(max_side, area_origins, 10000)) 79 | -------------------------------------------------------------------------------- /2016/day_23.py: -------------------------------------------------------------------------------- 1 | """Advent of Code Day 23 - Safe Cracking""" 2 | 3 | from collections import defaultdict 4 | 5 | 6 | def is_num(s): 7 | """Determine whether a string is a number or not.""" 8 | try: 9 | int(s) 10 | except ValueError: 11 | return False 12 | return True 13 | 14 | 15 | def do_ops(start_a): 16 | """Carry out operations and return the value of register 'a'.""" 17 | registers = defaultdict(int) 18 | registers['a'] = start_a 19 | i = 0 20 | while i < len(ops): 21 | if ops[i][0] == 0: 22 | if is_num(ops[i][1]): 23 | registers[ops[i][2]] = ops[i][1] 24 | else: 25 | registers[ops[i][2]] = registers[ops[i][1]] 26 | 27 | elif ops[i][0] == 1: 28 | registers[ops[i][1]] += 1 29 | 30 | elif ops[i][0] == 2: 31 | registers[ops[i][1]] -= 1 32 | 33 | elif ops[i][0] == 3: 34 | if is_num(ops[i][1]): 35 | if ops[i][1] != 0: 36 | if is_num(ops[i][2]): 37 | i += ops[i][2] - 1 38 | else: 39 | i += registers[ops[i][2]] - 1 40 | 41 | elif registers[ops[i][1]] != 0: 42 | if is_num(ops[i][2]): 43 | i += ops[i][2] - 1 44 | else: 45 | i += registers[ops[i][2]] - 1 46 | 47 | elif ops[i][0] == 4: 48 | jump = i + registers[ops[i][1]] 49 | if jump >= len(ops): 50 | i += 1 51 | continue 52 | 53 | if len(ops[jump]) == 2: 54 | if ops[jump][0] == 1: 55 | ops[jump][0] = 2 56 | else: 57 | ops[jump][0] = 1 58 | 59 | else: 60 | if ops[jump][0] == 3: 61 | ops[jump][0] = 0 62 | else: 63 | ops[jump][0] = 3 64 | 65 | elif ops[i][0] == 5: 66 | # For part two found slow loop and modified it with this and NOPs 67 | registers['a'] = registers['b'] * registers['d'] 68 | 69 | i += 1 70 | 71 | return registers['a'] 72 | 73 | 74 | op_conv = { 75 | 'cpy': 0, 76 | 'inc': 1, 77 | 'dec': 2, 78 | 'jnz': 3, 79 | 'tgl': 4, 80 | 'mul': 5 81 | } 82 | 83 | with open('inputs/day_23.txt') as f: 84 | ops = [line.strip().split(' ') for line in f.readlines()] 85 | 86 | for op in ops: 87 | op[0] = op_conv[op[0]] 88 | if is_num(op[1]): 89 | op[1] = int(op[1]) 90 | if len(op) == 3 and is_num(op[2]): 91 | op[2] = int(op[2]) 92 | 93 | # Answer One (Change argument to 7) / Answer Two 94 | print("Register a:", do_ops(12)) 95 | -------------------------------------------------------------------------------- /2020/day_22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Advent of Code 2020 Day 22 - Crab Combat.""" 4 | 5 | 6 | def play_game(player_1: list, player_2: list, part_two: bool=False) -> tuple: 7 | """Play a game of (Recursive) Combat returning winner and their hand. 8 | 9 | Args: 10 | player_1: List of player one's card values. 11 | player_2: List of player two's card values. 12 | 13 | Return: 14 | Tuple of winner's number (1/2) and their winning card values in order. 15 | 16 | """ 17 | prev_rounds = set() 18 | while player_1 and player_2: 19 | one_card = player_1[0] 20 | two_card = player_2[0] 21 | if (tuple(player_1), tuple(player_2)) in prev_rounds: 22 | player_1.extend([one_card, two_card]) 23 | return (1, player_1) 24 | prev_rounds.add((tuple(player_1), tuple(player_2))) 25 | 26 | if not part_two: 27 | if one_card > two_card: 28 | player_1.extend([one_card, two_card]) 29 | else: 30 | player_2.extend([two_card, one_card]) 31 | else: 32 | if len(player_1) - 1 >= one_card and len(player_2) - 1 >= two_card: 33 | one_recursive = player_1[1: one_card + 1].copy() 34 | two_recursive = player_2[1: two_card + 1].copy() 35 | winner = play_game(one_recursive, two_recursive, True) 36 | if winner[0] == 1: 37 | player_1.extend([one_card, two_card]) 38 | else: 39 | player_2.extend([two_card, one_card]) 40 | 41 | else: 42 | if one_card > two_card: 43 | player_1.extend([one_card, two_card]) 44 | else: 45 | player_2.extend([two_card, one_card]) 46 | 47 | player_1 = player_1[1:] 48 | player_2 = player_2[1:] 49 | 50 | if player_1: 51 | return (1, player_1) 52 | else: 53 | return (2, player_2) 54 | 55 | 56 | with open('inputs/day_22.txt') as f: 57 | player_1, player_2 = [line for line in f.read().split('\n\n')] 58 | player_1 = [int(x) for x in player_1.split('\n')[1:]] 59 | player_2 = [int(x) for x in player_2.split('\n')[1:-1]] 60 | 61 | winner, winning_order = play_game(player_1.copy(), player_2.copy()) 62 | winning_score = 0 63 | for rev_index, card in enumerate(winning_order[::-1], 1): 64 | winning_score += rev_index * card 65 | 66 | # Answer One 67 | print("Combat winner's score:", winning_score) 68 | 69 | winner, winning_order = play_game(player_1, player_2, part_two=True) 70 | winning_score = 0 71 | for rev_index, card in enumerate(winning_order[::-1], 1): 72 | winning_score += rev_index * card 73 | 74 | # Answer Two 75 | print("Recursive Combat winner's score:", winning_score) 76 | --------------------------------------------------------------------------------