Learning Python with Advent of Code Walkthroughs

Dazbo's Advent of Code solutions, written in Python

Signals and Noise

Advent of Code 2016 - Day 6

Day 6: Signals and Noise

Useful Links

Concepts and Packages Demonstrated

collections.CounterzipString ManipulationLogging

Problem Intro

The year is 2016, and we’re helping Santa’s communication system. Due to a local electromagnetic anomaly, messages are being corrupted. We receive a series of messages, but each character in the message has been sent many times, and the most frequent character in each position is the correct one.

Our input consists of a list of corrupted messages, one per line. For example:

eedadn
drvtee
eandsr
raavrd
atevrs
tsrnev
sdttsa
rasrtv
nssdts
ntnada
svetve
tesnvf
vntsnd
vrdear
dvrsen
enarar

Part 1

Given the corrupted messages, what is the error-corrected version of the message?

To solve this, we need to determine the most frequent character in each column of the input data. The problem can be broken down into these steps:

  1. Read all the lines of the input data.
  2. “Transpose” the data so that we can easily access all characters in a given column.
  3. For each column, count the occurrences of each character.
  4. Identify the most frequent character in that column.
  5. Combine these characters to form the error-corrected message.

Here’s the core logic from the solution:

import logging
import os
import time
from collections import Counter

SCRIPT_DIR = os.path.dirname(__file__) 
INPUT_FILE = "input/input.txt"

def main():
    logging.basicConfig(level=logging.DEBUG, format="%(asctime)s:%(levelname)s:\t%(message)s")
        
    input_file = os.path.join(SCRIPT_DIR, INPUT_FILE)
    with open(input_file, mode="rt") as f:
        data = f.read().splitlines()
        
    # First, we need to transpose columns to rows.
    # zip(*data) takes an iterable of iterables and aggregates the elements
    # from each of the iterables. For example, if data = ['abc', 'def'],
    # then zip(*data) would produce [('a', 'd'), ('b', 'e'), ('c', 'f')].
    transposed = list(zip(*data))
    
    most_common_chars = [] # Part 1
    
    for line in transposed:
        char_counts = Counter(line)
        # Counter.most_common(1) returns a list of the 1 most common elements
        # and their counts, e.g., [('e', 5)]. We want just the character.
        most_common_chars.append(char_counts.most_common(1)[0][0])
    
    most_common = "".join(str(char) for char in most_common_chars)
    
    logging.info(f"Part 1 message: {most_common}")

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

The key steps here are:

Part 2

As a second part of the communication system, we also need to find the original message if the communication system uses a modified repetition code where the character that appears least frequently in each position is the correct one.

Part 2 is a slight variation of Part 1. Instead of finding the most frequent character, we need to find the least frequent character in each column. The collections.Counter object doesn’t have a direct least_common method, but we can achieve this by sorting the items by their counts and picking the first one, or by using min with a custom key.

Here’s how the solution extends to handle Part 2:

    # ... (previous code for reading data and transposing)

    most_common_chars = [] # Part 1
    least_common_chars = [] # Part 2
        
    for line in transposed:
        char_counts = Counter(line)
        # Get the most frequent char (Part 1)
        most_common_chars.append(char_counts.most_common(1)[0][0])
        
        # Get the least frequent char (Part 2)
        # We find the minimum count using a lambda function as the key for min()
        least_common_chars.append(min(char_counts.items(), key=lambda x: x[1])[0])
    
    # Convert to str representation
    least_common = "".join(str(char) for char in least_common_chars)
    most_common = "".join(str(char) for char in most_common_chars)
    
    logging.info(f"Part 1 message: {most_common}")
    logging.info(f"Part 2 message: {least_common}")

The key change for Part 2 is the line:

This approach efficiently solves both parts of the puzzle by leveraging Python’s built-in zip function for transposition and collections.Counter for frequency analysis.