zerosleeps

Since 2010

Advent of Code 2020 day 10 part 2

Advent of Code 2020 day 10 part two. Not in the mood for this one. My solution has been stolen from /u/ald_loop’s incredibly thorough and generous explanation.

No amount of stepping through this line-by-line in a debugger has lifted the brain-fog. Obviously the code is programming-101 stuff, it’s simply that I’m unable to link the logic to the problem and the solution.

I’ll revisit this in the future, but for now I’m just going to let it go.

def part_two(parsed_input):
    # Add charging outlet and device to list of adapters
    parsed_input += [0, max(parsed_input)+3]
    parsed_input = sorted(parsed_input)

    dp = [0]*len(parsed_input)
    # There's only one path to the charging outlet
    dp[0] = 1
    for i in range(1, len(parsed_input)):
        sm = 0
        for j in range(1,4): # 1, 2, 3
            if i-j < 0:
                # Skip out-of-range checks at the start of the process
                continue
            if parsed_input[i]-parsed_input[i-j] <= 3:
                # Candidate adapter
                sm += dp[i-j]
        dp[i] = sm
    return dp[-1]

Advent of Code 2020 day 10

Advent of Code 2020 day 10 part one. I haven’t got a clue what knowledge I’m missing for part two. I have code that works fine with the example inputs, but it will take days to run for my actual input.

Folk on the subreddit are talking about concepts I’ve never heard of. Heck, even looking at some of the solutions for part one have me scratching my head. Can’t wrapt my head around the maths.

I’ll try again tomorrow. Here’s my Python solution for part one though:

from pathlib import Path

def get_raw_input():
    return (Path(__file__).parent/'day_10_input.txt').read_text()

def parse_raw_input(raw_input):
    return [int(line.strip()) for line in raw_input.strip().splitlines()]

def candidate_adapters(adapters, current_rating):
    return [adapter for adapter in adapters if (adapter > current_rating and adapter - current_rating <= 3)]

def part_one(parsed_input):
    current_rating = 0 # Initialised to charging outlet
    differences = {1: 0, 2: 0, 3: 0}
    while len(parsed_input) > 0:
        candidates = candidate_adapters(parsed_input, current_rating)
        next_adapter = sorted(candidates)[0]

        difference = next_adapter - current_rating
        differences[difference] += 1

        current_rating = next_adapter

        parsed_input.remove(next_adapter)

    differences[3] += 1 # For device's built-in adapter

    return differences

def distribution(differences):
    return differences[1] * differences[3]

if __name__ == '__main__':
    print(f'Part one: {distribution(part_one(parse_raw_input(get_raw_input())))}')

Advent of Code 2020 day 9

Advent of Code 2020 day 9. This one was good fun. Got myself into a right mess with variable names - overuse of the word “combination”!

from itertools import combinations
from pathlib import Path
import unittest

def get_raw_input():
    return (Path(__file__).parent/'day_09_input.txt').read_text()

def parse_raw_input(raw_input):
    return [int(line.strip()) for line in raw_input.strip().splitlines()]

def combination_options(full_list, end, preample_len):
    return full_list[end-preample_len:end]

def combinations_to_check(combination_options):
    return combinations(combination_options, 2)

def part_one(parsed_input, preample_len):
    i = preample_len
    while len([
        combination
        for combination
        in combinations_to_check(combination_options(parsed_input, i, preample_len))
        if sum(combination) == parsed_input[i]
    ]) != 0:
        i += 1

    return parsed_input[i]

def part_two(parsed_input, preample_len):
    invalid_number = part_one(parsed_input, preample_len)
    i = 0 # First number to check
    j = 2 # Consecutive numbers to check
    while (i + j) < len(parsed_input):
        while sum(parsed_input[i:i+j]) < invalid_number:
            j += 1 # Try a longer sequence

        if sum(parsed_input[i:i+j]) == invalid_number:
            return min(parsed_input[i:i+j]) + max(parsed_input[i:i+j])

        # Increment starting position and reset sequence length
        i += 1
        j = 2

class TestExamples(unittest.TestCase):
    def setUp(self):
        self.example_input = """35
            20
            15
            25
            47
            40
            62
            55
            65
            95
            102
            117
            150
            182
            127
            219
            299
            277
            309
            576"""

    def test_part_one(self):
        self.assertEqual(part_one(parse_raw_input(self.example_input), 5), 127)

    def test_part_one(self):
        self.assertEqual(part_two(parse_raw_input(self.example_input), 5), 62)

if __name__ == '__main__':
    print(f'Part one: {part_one(parse_raw_input(get_raw_input()), 25)}') # 776203571
    print(f'Part two: {part_two(parse_raw_input(get_raw_input()), 25)}') # 104800569

Advent of Code 2020 day 8

Advent of Code 2020 day 8. Happy with the logic, but not particularly happy with the implementation: lots of while loops and if statements that all look the same when you squint at the code.

Rather than reuse the same Console object for each iteration in part two and reset it’s state, I wish I’d created a new object for each iteration. But this works and I’m behind, so on to day 9.

from pathlib import Path
import unittest

def get_raw_input():
    return (Path(__file__).parent/'day_08_input.txt').read_text()

class Console():
    def __init__(self, raw_input):
        self.instructions = self.parse_raw_input(raw_input)
        self.accumulator = 0
        self.next_instruction = 0
        self.instruction_log = []
        self.mutate_line = -1

    @staticmethod
    def parse_raw_input(raw_input):
        return [
            (line.strip().split()[0], int(line.strip().split()[1]))
            for line
            in raw_input.strip().splitlines()
        ]

    def reset_state(self):
        self.accumulator = 0
        self.next_instruction = 0
        self.instruction_log = []

    def run(self):
        while self.next_instruction not in self.instruction_log:
            try:
                current_instruction = self.instructions[self.next_instruction]
            except IndexError:
                return (self.accumulator, 0)

            self.instruction_log.append(self.next_instruction)
            self.next_instruction += 1

            if current_instruction[0] == 'acc':
                self.accumulator += int(current_instruction[1])
            elif current_instruction[0] == 'jmp':
                self.next_instruction += (int(current_instruction[1]) - 1)
            elif current_instruction[0] == 'nop':
                pass

        return self.accumulator

    def mutate_input(self):

        last_line_mutated = self.mutate_line

        if last_line_mutated >= 0:
            # Revert previous mutation
            if self.instructions[last_line_mutated][0] == 'nop':
                self.instructions[last_line_mutated] = ('jmp', self.instructions[last_line_mutated][1])
            else:
                self.instructions[last_line_mutated] = ('nop', self.instructions[last_line_mutated][1])

            self.reset_state()

        self.mutate_line += 1

        while self.instructions[self.mutate_line][0] not in ['nop', 'jmp']:
            self.mutate_line += 1

        if self.instructions[self.mutate_line][0] == 'nop':
            self.instructions[self.mutate_line] = ('jmp', self.instructions[self.mutate_line][1])
        else:
            self.instructions[self.mutate_line] = ('nop', self.instructions[self.mutate_line][1])
        return True

def part_one(raw_input):
    return Console(raw_input).run()

def part_two(raw_input):
    c = Console(raw_input)
    while type(c.run()) != tuple:
        c.mutate_input()
    return c.accumulator

class TestExamples(unittest.TestCase):
    def setUp(self):
        self.example_input = """nop +0
            acc +1
            jmp +4
            acc +3
            jmp -3
            acc -99
            acc +1
            jmp -4
            acc +6"""

    def test_part_one(self):
        self.assertEqual(part_one(self.example_input), 5)

    def test_part_one(self):
        self.assertEqual(part_two(self.example_input), 8)

if __name__ == '__main__':
    print(f'Part one: {part_one(get_raw_input())}')
    print(f'Part two: {part_two(get_raw_input())}')

Advent of Code 2020 day 7

Advent of Code 2020 day 7. I really struggled with this one. I never studied this sort of tree traversal stuff, and I’ve never had to do anything like it so it was all guess work. Pretty satisfying to finally figure it out though!

from pathlib import Path
import re
import unittest

def get_raw_input():
    return (Path(__file__).parent/'day_07_input.txt').read_text()

def get_bag_from_input(input):
    return input.strip().split(' bags contain ')[0]

def get_contents_from_input(input):
    if re.search(r'no other bags', input):
        return {}
    else:
        return {
            ' '.join(bag.split()[1:-1]): int(bag.split()[0])
            for bag
            in input.strip().split(' bags contain ')[1].rstrip('.').split(', ')
        }

def parents_for_target(target, rules):
    return [bag for bag, contents in rules.items() if target in contents]

def number_of_children(bag, all_rules):
    count = 0
    if len(all_rules[bag]) > 0:
        for b, q in all_rules[bag].items():
            count += ( q * number_of_children(b, all_rules))
    return count + 1 # Plus one to account for current bag as well

def part_one(raw_input):
    rules = {
        get_bag_from_input(line):get_contents_from_input(line)
        for line
        in raw_input.strip().splitlines()
    }

    candidates = set(parents_for_target('shiny gold', rules))

    growth = True
    while growth:
        new_candidates = { b for c in candidates for b in parents_for_target(c, rules) }
        if new_candidates <= candidates:
            growth = False
        candidates.update(new_candidates)

    return len(candidates)

def part_two(raw_input):
    all_rules = {
        get_bag_from_input(line):get_contents_from_input(line)
        for line
        in raw_input.strip().splitlines()
    }

    return number_of_children('shiny gold',all_rules) - 1

class TestExamples(unittest.TestCase):
    def setUp(self):
        self.example_input = """light red bags contain 1 bright white bag, 2 muted yellow bags.
            dark orange bags contain 3 bright white bags, 4 muted yellow bags.
            bright white bags contain 1 shiny gold bag.
            muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.
            shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.
            dark olive bags contain 3 faded blue bags, 4 dotted black bags.
            vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
            faded blue bags contain no other bags.
            dotted black bags contain no other bags.
        """

    def test_part_one(self):
        self.assertEqual(part_one(self.example_input), 4)

    def test_part_two_example_one(self):
        self.assertEqual(part_two(self.example_input), 32)

    def test_part_two_example_two(self):
        example_input = """shiny gold bags contain 2 dark red bags.
            dark red bags contain 2 dark orange bags.
            dark orange bags contain 2 dark yellow bags.
            dark yellow bags contain 2 dark green bags.
            dark green bags contain 2 dark blue bags.
            dark blue bags contain 2 dark violet bags.
            dark violet bags contain no other bags.
        """
        self.assertEqual(part_two(example_input), 126)

if __name__ == '__main__':
    print(f'Part one: {part_one(get_raw_input())}')
    print(f'Part two: {part_two(get_raw_input())}')