Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Rucksacks

Advent of Code 2022 - Day 3

Day 3: Rucksack Reorganization

Useful Links

Concepts and Packages Demonstrated

defaultdictenumeratesetsComprehension

Problem Intro

This problem is about looking at the items in rucksacks, and looking for common items.

The input data looks something like this:

vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw

Part 1

Find the item type that appears in both compartments of each rucksack. What is the sum of the priorities of those item types?

First, let’s build a dictionary that maps all the item characters to their corresponding priority values:

    item_to_priority = {} # a:1, b:2... Y:51, Z:52
    for i, ordinal in enumerate(range(ord('a'), ord('z')+1), start=1):
        item_to_priority[chr(ordinal)] = i
    for i, ordinal in enumerate(range(ord('A'), ord('Z')+1), start=27):
        item_to_priority[chr(ordinal)] = i

This works by:

Next:

    priorities = []
    for rucksack in data:
        compartment_1 = set(rucksack[0:len(rucksack)//2])
        compartment_2 = set(rucksack[len(rucksack)//2:])
        common = compartment_1 & compartment_2 # intersection
        for item in common:
            priorities.append(item_to_priority[item])
    
    print(f"Part 1: Sum of priorities = {sum(priorities)}")

What this does:

Easy!

Part 2

Find the item type that corresponds to the badges of each three-Elf group. What is the sum of the priorities of those item types?

We’re told that each successive group of three input lines represents an elf group. We need to find items that are common to all three rucksacks in each group. Then we add up the priorities of these items, as before.

First, I’ll create dictionary, where the key is the group number, and the value is list of the three rucksacks that make up that group. I’m building the dictionary using defaultdict:

    # Goal is # {1: ['vJrw...', 'jqHR...', 'Pmmd...'], 2: [...], ...} etc
    groups = defaultdict(list) 
    group_num = 1
    for line_num, rucksack in enumerate(data):
        if line_num > 0 and line_num % GRP_SZ == 0:
            group_num += 1
        
        groups[group_num] += [rucksack]

Note that we once again use enumerate() to get the current line number. And when each line number is a multiple of GRP_SZ (which is 3), we increment the group number.

Finally, we’re ready to iterate over each group, and find the items that are common in each group:

    priorities = []
    for rucksacks in groups.values():
        rucksack_sets = [set(sack) for sack in rucksacks]
        common = set.intersection(*rucksack_sets)
        for item in common:
            priorities.append(item_to_priority[item])        
        
    print(f"Part 2: Sum of priorities = {sum(priorities)}")

Recall that each group is a dictionary. Recall that each dictionary value is a list of three rucksacks. I convert each rucksack to a set, using list comprehension. Then we use the cool set.intersection() method, which allows us intersect (i.e. find the commonality of) any number of sets using the splat * operator. This gives us the items that are common to all three rucksacks.

Finally, we can convert these items to priorities, as before.

Results

The final code looks like this:

from collections import defaultdict
from pathlib import Path
import time

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

GRP_SZ = 3

def main():
    with open(INPUT_FILE, mode="rt") as f:
        data = f.read().splitlines()
        
    item_to_priority = {} # a:1, b:2... Y:51, Z:52
    for i, ordinal in enumerate(range(ord('a'), ord('z')+1), start=1):
        item_to_priority[chr(ordinal)] = i
    for i, ordinal in enumerate(range(ord('A'), ord('Z')+1), start=27):
        item_to_priority[chr(ordinal)] = i
    
    priorities = []
    for rucksack in data:
        compartment_1 = set(rucksack[0:len(rucksack)//2])
        compartment_2 = set(rucksack[len(rucksack)//2:])
        common = compartment_1 & compartment_2 # intersection
        for item in common:
            priorities.append(item_to_priority[item])
    
    print(f"Part 1: Sum of priorities = {sum(priorities)}")
    
    # Goal is # {1: ['vJrw...', 'jqHR...', 'Pmmd...'], 2: [...], ...} etc
    groups = defaultdict(list) 
    group_num = 1
    for line_num, rucksack in enumerate(data):
        if line_num > 0 and line_num % GRP_SZ == 0:
            group_num += 1
        
        groups[group_num] += [rucksack]
    
    priorities = []
    for rucksacks in groups.values():
        rucksack_sets = [set(sack) for sack in rucksacks]
        common = set.intersection(*rucksack_sets)
        for item in common:
            priorities.append(item_to_priority[item])        
        
    print(f"Part 2: Sum of priorities = {sum(priorities)}")
    
if __name__ == "__main__":
    t1 = time.perf_counter()
    main()
    t2 = time.perf_counter()
    print(f"Execution time: {t2 - t1:0.4f} seconds")

The output looks like this:

Part 1: Sum of priorities = 8240
Part 2: Sum of priorities = 2587
Execution time: 0.0015 seconds