Sean McLemon | Advent of Code

Home | Czech | Blog | GitHub | Advent Of Code | Notes


2017-12-21 - Fractal Art

(original .ipynb)
%%time
# printing the time because apparently many other people's solutions to part 2 were slow
# but mine is relatively speedy

puzzle_input_str = open("puzzle_input/day21.txt").read()
test_input_str = """../.# => ##./#../...
.#./..#/### => #..#/..../..../#..#"""

start_pattern = [
    list(".#."),
    list("..#"),
    list("###")
]


def rotations_2x2(grid, rotations=4):
    a, b = grid[0]
    d, c = grid[1]
    new_grid = [
        [d, a],
        [c, b]
    ]

    yield new_grid
    if rotations > 0:
        yield from rotations_2x2(new_grid, rotations-1)


def rotations_3x3(grid, rotations=3):
    yield grid
    a, b, c = grid[0]
    h, x, d = grid[1]
    g, f, e = grid[2]
    new_grid = [
        [g, h, a],
        [f, x, b],
        [e, d, c],
    ]
        
    if rotations > 0:
        yield from rotations_3x3(new_grid, rotations-1)


def flips_2x2(grid):
    r1, r2 = grid
    yield grid
    yield [r2, r1]
    yield [r1[::-1], r2[::-1]]


def flips_3x3(grid):
    r1, r2, r3 = grid
    yield grid
    yield [r3, r2, r1]
    yield [r1[::-1], r2[::-1], r3[::-1]]
    

def combinations_2x2(base_grid):
    yield base_grid
    for grid in flips_2x2(base_grid):
        yield from rotations_2x2(grid)


def combinations_3x3(base_grid):
    yield base_grid
    for grid in flips_3x3(base_grid):
        yield from rotations_3x3(grid)        


def combinations(base_grid):
    if len(base_grid[0]) == 2:
        return combinations_2x2(base_grid)
    elif len(base_grid[0]) == 3:
        return combinations_3x3(base_grid)

    
def stringify(grid):
    return "".join(
        "".join(row) for row in grid
    )


def parse_rules(rules_str):
    rule_lines = rules_str.strip().split("\n")
    rules = {}
    
    for rule_line in rule_lines:
        raw_pattern, raw_replacement = rule_line.split(" => ")
        base_pattern = raw_pattern.split("/")
        replacement = raw_replacement.split("/")
        
        for pattern in combinations(base_pattern):
            rules[stringify(pattern)] = replacement
        
    return rules


def subgrid_2x2(grid, r, c):
    base_r = 2 * r
    base_c = 2 * c
    return [
        grid[base_r + 0][base_c:base_c+2],
        grid[base_r + 1][base_c:base_c+2]
    ]


def subgrid_3x3(grid, r, c):
    base_r = 3 * r
    base_c = 3 * c
    return [
        grid[base_r + 0][base_c:base_c+3],
        grid[base_r + 1][base_c:base_c+3],
        grid[base_r + 2][base_c:base_c+3]
    ]


def enhance_grid_base(grid, rules, subgrid_func, n):
    size = len(grid) // n
    new_grid = []
    for r in range(size):
        new_row = []
        for c in range(size):
            subgrid = subgrid_func(grid, r, c)
            pattern = stringify(subgrid)
            if pattern in rules:
                new_row.append(rules[pattern])
            else:
                raise Exception(f"{pattern} does not exist in rules")
        new_grid.append(new_row)
    return flatten(new_grid)


def enhance_grid(grid, rules):
    if len(grid) % 2 == 0:
        return enhance_grid_base(grid, rules, subgrid_2x2, 2)
    if len(grid) % 3 == 0:
        return enhance_grid_base(grid, rules, subgrid_3x3, 3)
    raise Exception("The grid is a weird size")
    

def flatten(grid):
    assert len(grid) == len(grid[0])
    new_grid = []
    for row in grid:
        new_rows = [""] * len(row[0])
        for subgrid in row:
            for i, subgrid_row in enumerate(subgrid):
                new_rows[i] += subgrid_row
        new_grid += new_rows
    return new_grid


def count_on(pattern):
    on = 0
    for row in pattern:
        on += sum(1 for c in row if c != ".")
    return on    


def solve(input_str, iterations):
    rules = parse_rules(input_str)    
    pattern = start_pattern

    while iterations > 0:
        pattern = enhance_grid(pattern, rules)
        iterations -= 1

    return count_on(pattern)


assert 12 == solve(test_input_str, 2)
print("part one:", solve(puzzle_input_str, 5))
print("part two:", solve(puzzle_input_str, 18))
part one: 197
part two: 3081737
CPU times: user 3.36 s, sys: 23.8 ms, total: 3.39 s
Wall time: 3.45 s