<jupyter_start><jupyter_text>Day 20 - Trench Maphttps://adventofcode.com/2021/day/20<jupyter_code>from pathlib import Path

INPUTS = Path("input.txt").read_text().strip().split("\n")

ENHANCER = INPUTS[0]
IMAGE = INPUTS[2:]

def section_to_decimal(section: str) -> int:
    output = ''.join('1' if x == '#' else '0' for x in section)
    return int(output, base=2)

assert section_to_decimal(section='...#...#.') == 34
def enhance_image(
    original: list[str],
    enhancer: str = ENHANCER,
    padder: str = ".",
) -> list[str]:
    extra_row = padder * (len(original[0]) + 4)
    # Expand the original 2 pixels in every dimension
    # to more easily grab sections on the edges for enhancing the final image.
    new_original = [
        extra_row,
        extra_row,
        *[f"{padder*2}{x}{padder*2}" for x in original],
        extra_row,
        extra_row,
    ]
    output = []

    for i in range(len(new_original) - 2):
        outrow = ""
        for j in range(len(new_original[0]) - 2):
            section = "".join(x[j : j + 3] for x in new_original[i : i + 3])
            index = section_to_decimal(section=section)
            outrow += enhancer[index]
        output.append(outrow)
    return output

def test_enhance_image():
    enhancer = (
        "..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..##"
        "#..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###"
        ".######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#."
        ".#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#....."
        ".#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.."
        "...####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#....."
        "..##..####..#...#.#.#...##..#.#..###..#####........#..####......#..#"
    )
    image = [
        "#..#.",
        "#....",
        "##..#",
        "..#..",
        "..###",
    ]
    enhanced = enhance_image(original=image, enhancer=enhancer)
    expected = [
        ".##.##.",
        "#..#.#.",
        "##.#..#",
        "####..#",
        ".#..##.",
        "..##..#",
        "...#.#.",
    ]
    assert enhanced == expected, "Output:\n" + "\n".join(enhanced)

    enhanced2 = enhance_image(original=enhanced, enhancer=enhancer, padder=enhancer[0])
    expected2 = [
        ".......#.",
        ".#..#.#..",
        "#.#...###",
        "#...##.#.",
        "#.....#.#",
        ".#.#####.",
        "..#.#####",
        "...##.##.",
        "....###..",
    ]
    assert enhanced2 == expected2, "Output:\n" + "\n".join(enhanced2)
    light_pixels = sum([sum([1 for y in x if y == "#"]) for x in enhanced2])
    assert light_pixels == 35, light_pixels


test_enhance_image()<jupyter_output><empty_output><jupyter_text>I had some trouble at the next stage with AoC site telling me my count was off, despite everything seeming to work correctly. What I didn't account for was that the enhancement of a pixel surrounded by all dark pixels results in index `0`, and index 0 of my enhancer was a *light* pixel. This meant that every dark pixel in infinite directions would alternate between light and dark on each iteration (subsequently, the 512th pixel in the enhancer is ``, completing the alternating pattern, as all light pixels results in that final position in the enhancer).The fix for this is to adjust the enhancement algorithm so that it adds two new rows and columns on the outsides matching the first pixel in the enhancer on every *even* enhancement. This ensured that even the example code worked the same and that my own enhancer worked correctly.This gotcha had me stumped in part 1 for a while, but a couple lines of code later and it's solved.<jupyter_code>pass1 = enhance_image(original=IMAGE, enhancer=ENHANCER)
# As noted above, the second pass has to use the first pixel in the ENHANCER as a padder
# in order to get back the correct image.
pass2 = enhance_image(original=pass1, enhancer=ENHANCER, padder=ENHANCER[0])<jupyter_output><empty_output><jupyter_text>Once we had a final image (in `pass2` above), we have to count the light pixels. This was a simple matter of flattening and summing all instances of `` in the final list of strings.I could have simplified ever so slightly had I converted the pixels to `1`s and `0`s first, but where's the fun in that?<jupyter_code>light_pixels = sum([sum([1 for y in x if y == '#']) for x in pass2])
print(f"Number of light pixels: {light_pixels}")<jupyter_output>Number of light pixels: 5057<jupyter_text>Part 2Running the same algorithm 50x isn't much of a deal compared to running it 2x. We just need to be sure to pull the correct padding character on even-numbered iterations, so we have the `padder` line flipping on the result of `i % 2` (if 1, it's `True`, meaning `i == 1` and `3`, which are indices `2` and `4` etc., which are our even-numbered iterations).<jupyter_code>image = IMAGE
iterations = 50
for i in range(iterations):
    padder = ENHANCER[0] if i % 2 else "."
    image = enhance_image(original=image, enhancer=ENHANCER, padder=padder)

light_pixels = sum([sum([1 for y in x if y == '#']) for x in image])
print(f"Number of light pixels: {light_pixels}")<jupyter_output>Number of light pixels: 18502
{'fraction_non_alphanumeric': 0.2233800682076544, 'fraction_numerical': 0.014020462296324365, 'mean_word_length': 2.557277628032345, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 17, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '668526', 'license': 'MIT', 'n_tokens_mistral': 1824, 'n_tokens_neox': 1700, 'n_words': 520, 'path': '2021/day20/main.ipynb', 'repo_name': 'GriceTurrble/advent-of-code-2020-answers', 'size': '5278'}