Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Bottles

Advent of Code 2015 - Day 17

Day 17: No Such Thing as Too Much

Useful Links

Concepts and Packages Demonstrated

List comprehensionCombinations

Problem Intro

We have 150L of eggnog, and we need to distribute this eggnog over a bunch of containers. The volumes of the containers (in L) are our puzzle input. E.g.

20
15
10
5
5

Part 1

Filling all containers entirely, how many different combinations of containers can exactly fit all 150 liters of eggnog?

This is quite an easy problem. I used a fairly similar solution to Day 15.

First, we read in the numbers, and convert them to a single list of int values:

    with open(INPUT_FILE, mode="rt") as f:
        containers = [int(x) for x in f.read().splitlines()]

Then:

The code looks like this:

    all_valid_combos = []  # all the container combos that add up to TARGET
    
    # Try any single container, then any two containers, then any three, etc
    for num_containers in range(1, len(containers)+1):
        valid_combos = [combo for combo in list(combinations(containers, num_containers))
                              if sum(combo) == TARGET]
        all_valid_combos.extend(valid_combos)

    # part 1
    print(f"Part 1: Valid combos that contain {TARGET}L={len(all_valid_combos)}")

Easy!

Part 2

Find the minimum number of containers that can exactly fit all 150 liters of eggnog. How many different ways can you fill that number of containers and still hold exactly 150 litres?

Well, this is very easy! We just need to do exactly the same as Part 1, but stop once we’ve got our first valid combinations. Why? Because at that point, we’ve already got the combinations using hte fewest number of containers!

    # part 2
    all_valid_combos = []  # all the container combos that add up to TARGET
    for num_containers in range(1, len(containers)+1):
        valid_combos = [combo for combo in list(combinations(containers, num_containers))
                              if sum(combo) == TARGET]
        all_valid_combos.extend(valid_combos)
        if valid_combos:
            break
    
    print(f"Part 2: Combinations with minimum number of containers={len(all_valid_combos)}")

Results

Here’s the complete code:

from pathlib import Path
from itertools import combinations
import time

SCRIPT_DIR = Path(__file__).parent
# INPUT_FILE = Path(SCRIPT_DIR, "input/sample_input.txt")
INPUT_FILE = Path(SCRIPT_DIR, "input/input.txt")

TARGET = 150

def main():
    with open(INPUT_FILE, mode="rt") as f:
        containers = [int(x) for x in f.read().splitlines()]

    all_valid_combos = []  # all the container combos that add up to TARGET
    
    # Try any single container, then any two containers, then any three, etc
    for num_containers in range(1, len(containers)+1):
        valid_combos = [combo for combo in list(combinations(containers, num_containers))
                              if sum(combo) == TARGET]
        all_valid_combos.extend(valid_combos)

    # part 1
    print(f"Part 1: Valid combos that contain {TARGET}L={len(all_valid_combos)}")

    # part 2
    all_valid_combos = []  # all the container combos that add up to TARGET
    for num_containers in range(1, len(containers)+1):
        valid_combos = [combo for combo in list(combinations(containers, num_containers))
                              if sum(combo) == TARGET]
        all_valid_combos.extend(valid_combos)
        if valid_combos:
            break
    
    print(f"Part 2: Combinations with minimum number of containers={len(all_valid_combos)}")

if __name__ == "__main__":
    t1 = time.perf_counter()
    main()
    t2 = time.perf_counter()
    print(f"Execution time: {t2 - t1:0.4f} seconds")

And here’s the output:

Part 1: Valid combos that contain 150L=4372
Part 2: Combinations with minimum number of containers=4
Execution time: 0.1281 seconds