Advent of Code 2020 in Python, Day 1

Published on .

I’m late to Advent of Code this year, but I thought I’d post my solution anyway. I can’t say if I’ll continue doing more of these (and posting them here) or not — I’m well-rested and have the energy after a week of vacation tonight, but I return to work on Monday and may get busy with other things in life.

The prompt for day 1 asks you, in the first part, to find the two integers in the input data that add to 2020, and multiply them together and return the product.

In my first solution, I wrote two functions. The first takes an integer term, a list of integer terms, and a desired total and returns the first integer from the list of terms that satisfies term + x = total.

def find_addend_to_sum_to_num(term1, terms, total=2020):
    """
    Given a term (integer) and a list of terms, find the other term in the list
    of terms to satisfy:
    term + ____ = total
    Returns an integer
    """
    matches = [x for x in terms if total - x == term1]
    if len(matches) < 1:
        return None
    return matches[0]

A second function takes the list of all terms and a desired total as arguments. It splits the terms into three lists: lower has all terms less than half of total; upper has all terms greater than half of total; and half has all terms that are half of total. I split it because any solution for two terms will either involve one number less than half of the total and one number greater than half of the total, or two numbers that are both exactly half of the total. If there are two items in half, that’s the solution. Otherwise, loop through the terms that are less than half of the total and use find_addend_to_sum_to_num to find a matching term from the upper terms.

def find_terms_adding_to_num(terms, total=2020):
    """
    Given a list of `terms`, find the two that add to `total`
    Returns a tuple of integers
    """
    lower = [x for x in terms if x <= total / 2]
    upper = [x for x in terms if x > total / 2]
    half = [x for x in lower if x == total / 2]

    if len(half) >= 2:
        return (half[0], half[1])

    for term in lower:
        match = find_addend_to_sum_to_num(term, upper, total=2020)
        if match:
            return (term, match)

    return (None, None)

Part 2 is a variation on the theme, asking for three terms summing to 2020 instead of two.

At first I wrote another function to solve part 2 that works on a similar principle to finding_terms_adding_to_num, except instead of looping through all the lower terms it recursively loops through every term, since that trick doesn’t work if you’re dividing it in more than two pieces, and then loops through every other term in a while loop, checking for a matching third term with find_addend_to_sum_to_num.

def solve_part_2(terms, total=2020):
    term1 = terms.pop()
    working_terms = list(terms)
    while len(working_terms) > 0:
        term2 = working_terms.pop()
        if term1 + term2 < total:
            match = find_addend_to_sum_to_num(term1 + term2, working_terms,
                                              total=total)
            if match:
                return (term1, term2, match)

    return solve_part_2(terms, total=total)

These solutions worked, but I realized I could refactor the recursive solution to part 2 to work for any number of terms and solve both parts with one function.

def find_terms_adding_to_x(terms, n=2, total=2020):
    """ Given a list of integer `terms`, find the `n` terms that add
    to `total`
    Return those `n` terms as a tuple of integers.
    """
    if len(terms) == 0:
        return []

    if n == 1:
        if total in terms:
            return [total]
        return []

    working_terms = list(terms)
    term1 = working_terms.pop()
    remaining_terms = list(working_terms)
    matches = find_terms_adding_to_x(remaining_terms,
                                     n=n - 1,
                                     total=total - term1)
    if matches:
        return [term1] + matches

    return find_terms_adding_to_x(working_terms, n=n, total=total)

This simplifies things significantly — it can find any number of terms that sum to some total, and it no longer needs the separate find_addends_to_sum_to_num function. It works in basically the same way as the solve_part_2 function, except it replaces the while loop with pure recursion.

This wrapper function calls it and returns the solution:

def solve(terms, n=2, total=2020):
    """ Given a list of integer `terms`, find the `n` terms that add
    to `total` and return the product of those terms multiplied together """
    return reduce(mul, find_terms_adding_to_x(terms, n=n, total=total))