def part_1(steps=1000):
    bodies = [
        Body(Point3(-1, -4, 0)),
        Body(Point3(4, 7, -1)),
        Body(Point3(-14, -10, 9)),
        Body(Point3(1, 2, 17)),
    ]
    last(simulate(bodies, steps_limit=steps))
    total_energy = sum(b.energy for b in bodies)
    print(f"part 1: total energy after {steps} steps is {total_energy}")
Exemple #2
0
def part_1(starting_number: str, iterations: int = 40) -> str:
    """
    Today, the Elves are playing a game called **look-and-say**. They take turns making sequences by
    reading aloud the previous sequence and using that reading as the next sequence.

    For example, `211` is read as "one two, two ones", which becomes `1221` (1x `2`, 2x `1`s).

        >>> las('211')
        '1221'

    Look-and-say sequences are generated iteratively, using the previous value as input for the next
    step. For each step, take the previous value, and replace each run of digits (like `111`) with
    the number of digits (`3`) followed by the digit itself (`1`).

    For example:

        >>> list(las_sequence('1', iterations=5))
        ['1', '11', '21', '1211', '111221', '312211']

    Starting with the digits in your puzzle input, apply this process **40 times**.
    What is the length of the result?

        >>> part_1('1')  # doctest: +ELLIPSIS
        part 1: after 40 iterations, sequence has length 82350
        '111312211312111322212321121113121112131112132112311321322112111312212321121113122112131...'
    """

    seq = last(las_sequence(starting_number, iterations))
    result = len(seq)
    print(
        f"part 1: after {iterations} iterations, sequence has length {result}")
    return seq
    def mark(self,
             *numbers: int,
             stop_at_first_win: bool = True) -> Board | None:
        winning_boards = (board for number in numbers for board in self
                          if board.mark(number))

        if stop_at_first_win:
            return next(winning_boards, None)
        else:
            return last(winning_boards, None)
Exemple #4
0
def part_2(starting_number: str, iterations: int = 50) -> str:
    """
    Now, starting again with the digits in your puzzle input, apply this process **50 times**.
    What is the length of the new result?

        >>> part_2('1')  # doctest: +ELLIPSIS
        part 2: after 50 iterations, sequence has length 1166642
        '311311222113111231133211121312211231131112311211133112111312211213211312111322211231131...'
    """

    seq = last(las_sequence(starting_number, iterations))
    result = len(seq)
    more = " more" if len(starting_number) >= 100 else ""
    print(
        f"part 2: after {iterations}{more} iterations, sequence has length {result}"
    )
    return seq
def test_1b():
    bodies = [
        Body(Point3(-8,-10, 0)),
        Body(Point3( 5,  5,10)),
        Body(Point3( 2, -7, 3)),
        Body(Point3( 9, -8,-3)),
    ]

    last_state = last(simulate(bodies, steps_limit=100))

    assert last_state == (
        (Point3(  8, -12, -9), Vector3(-7,   3,  0)),
        (Point3( 13,  16, -3), Vector3( 3, -11, -5)),
        (Point3(-29, -11, -1), Vector3(-3,   7,  4)),
        (Point3( 16, -13, 23), Vector3( 7,   1,  1)),
    )
    assert sum(b.energy for b in bodies) == 1940
    print("passed test_1b")
def part_1(instructions: Iterable['Instr'] | str) -> int:
    """
    You're airdropped near **Easter Bunny Headquarters** in a city somewhere. "Near", unfortunately,
    is as close as you can get - the instructions on the Easter Bunny Recruiting Document the Elves
    intercepted start here, and nobody had time to work them out further.

    The Document indicates that you should start at the given coordinates (where you just landed)
    and face North. Then, follow the provided sequence: either turn left (`L`) or right (`R`)
    90 degrees, then walk forward the given number of blocks, ending at a new intersection.

    There's no time to follow such ridiculous instructions on foot, though, so you take a moment and
    work out the destination. Given that you can only walk on the street grid of the city, how far
    is the shortest path to the destination?

    For example:

      - Following `R2, L3` leaves you `2` blocks East and `3` blocks North, or `5` blocks away:

        >>> last(walk('R2, L3'))
        (2, -3)
        >>> distance_from_origin(_)
        5

      - `R2, R2, R2` leaves you `2` blocks due South of your starting position, or `2` blocks away:

        >>> last(walk('R2, R2, R2'))
        (0, 2)

    **How many blocks away is Easter Bunny HQ?**

        >>> part_1('R5, L5, R5, R3')
        part 1: HQ is 12 blocks away at (10, -2)
        12
    """
    hq_pos = last(walk(instructions))
    distance = distance_from_origin(hq_pos)
    print(f"part 1: HQ is {distance} blocks away at {hq_pos}")
    return distance
Exemple #7
0
def part_1(step_size: int, count: int = 2017) -> int:
    """
    Suddenly, whirling in the distance, you notice what looks like a massive, pixelated hurricane:
    a deadly [spinlock](https://en.wikipedia.org/wiki/Spinlock). This spinlock isn't just consuming
    computing power, but memory, too; vast, digital mountains are being ripped from the ground and
    consumed by the vortex.

    If you don't move quickly, fixing that printer will be the least of your problems.

    This spinlock's algorithm is simple but efficient, quickly consuming everything in its path. It
    starts with a circular buffer containing only the value `0`, which it marks as the **current
    position**. It then steps forward through the circular buffer some number of steps (your puzzle
    input) before inserting the first new value, `1`, after the value it stopped on. The inserted
    value becomes the **current position**. Then, it steps forward from there the same number of
    steps, and wherever it stops, inserts after it the second new value, `2`, and uses that as
    the new **current position** again.

    It repeats this process of **stepping forward**, **inserting a new value**, and **using the
    location of the inserted value as the new current position** a total of **`2017`** times,
    inserting `2017` as its final operation, and ending with a total of `2018` values (including
    `0`) in the circular buffer.

    For example, if the spinlock were to step `3` times per insert, the circular buffer would begin
    to evolve like this (using parentheses to mark the current position after each iteration of the
    algorithm):

        >>> spinning = spinlock(step_size=3, rounds=2017)

      - The initial state before any insertions:

        >>> print_spin(next(spinning))
        (0)

      - The spinlock steps forward three times (`0`, `0`, `0`), and then inserts the first value,
        `1`, after it. `1` becomes the current position:

        >>> print_spin(next(spinning))
        0 (1)

      - The spinlock steps forward three times (`0`, `1`, `0`), and then inserts the second value,
        `2`, after it. `2` becomes the current position:

        >>> print_spin(next(spinning))
        0 (2) 1

      - The spinlock steps forward three times (`1`, `0`, `2`), and then inserts the third value,
        `3`, after it. `3` becomes the current position:

        >>> print_spin(next(spinning))
        0  2 (3) 1

    And so on:

        >>> for _ in range(6):
        ...     print_spin(next(spinning))
        0  2 (4) 3  1
        0 (5) 2  4  3  1
        0  5  2  4  3 (6) 1
        0  5 (7) 2  4  3  6  1
        0  5  7  2  4  3 (8) 6  1
        0 (9) 5  7  2  4  3  8  6  1

    Eventually, after 2017 insertions, the section of the circular buffer near the last insertion
    looks like this:

        >>> print_spin(last(spinning), context=3)
        1512  1134  151 (2017) 638  1513  851

    Perhaps, if you can identify the value that will ultimately be after the last value written
    (`2017`), you can short-circuit the spinlock. In this example, that would be `638`.

    **What is the value after `2017`** in your completed circular buffer?

        >>> part_1(step_size=3)
        part 1: value after 2017 is 638
        638
    """

    current_pos, numbers = last(spinlock(step_size, count))
    assert numbers[current_pos] == count
    result = numbers[current_pos + 1]
    print(f"part 1: value after 2017 is {result}")
    return result
def memory_game(numbers: list[int], turns: int) -> int:
    assert turns > 0
    game = islice(memory_game_it(numbers), turns)
    return last(tqdm(game, unit=" turns", total=turns, unit_scale=True, delay=1.0))
def generate_nth_key(salt: str, hasher: Hasher, nth: int) -> tuple[int, str]:
    keys = islice(generate_keys(salt, hasher), nth)
    desc = f"generating keys with {hasher.__name__}"
    return last(tqdm(keys, desc=desc, total=nth, unit=" keys", delay=1.0))
Exemple #10
0
def distance_after_steps(directions: Iterable[str]) -> int:
    origin = (0, 0)
    target = last(steps(origin, directions))
    return distance(origin, target)
def does_hit(initial_velocity: Vector, target: Rect) -> bool:
    return last(shoot(initial_velocity, target)) in target
def build_sequence(dependencies: Iterable[Dependency]) -> str:
    return ''.join(last(build(dependencies)).finished)
def floor_number(line: str) -> int:
    assert len(line) > 0
    return last(floors_from_str(line))
def part_1(grid: 'Grid', bursts: int = 10_000) -> int:
    """
    Diagnostics indicate that the local **grid computing cluster** has been contaminated with the
    **Sporifica Virus**. The grid computing cluster is a seemingly-infinite two-dimensional grid of
    compute nodes. Each node is either **clean** or **infected** by the virus.

    To [prevent overloading](https://en.wikipedia.org/wiki/Morris_worm#The_mistake) the nodes (which
    would render them useless to the virus) or detection by system administrators, exactly one
    **virus carrier** moves through the network, infecting or cleaning nodes as it moves. The virus
    carrier is always located on a single node in the network (the **current node**) and keeps track
    of the **direction** it is facing.

    To avoid detection, the virus carrier works in bursts; in each burst, it **wakes up**, does some
    **work**, and goes back to **sleep**. The following steps are all executed in order one time
    each burst:

      - If the **current node** is **infected**, it turns to its **right**. Otherwise, it turns to
        its **left**. (Turning is done in-place; the **current node** does not change.)
      - If the **current node** is **clean**, it becomes **infected**. Otherwise, it becomes
        **cleaned**. (This is done **after** the node is considered for the purposes of changing
        direction.)
      - The virus carrier moves **forward** one node in the direction it is facing.

    Diagnostics have also provided a **map of the node infection status** (your puzzle input).
    **Clean** nodes are shown as `.`; **infected** nodes are shown as `#`. This map only shows the
    center of the grid; there are many more nodes beyond those shown, but none of them are currently
    infected.

    The virus carrier begins in the middle of the map facing **up**.

    For example, suppose you are given a map like this:

        >>> example_grid = Grid.from_text('''
        ...
        ...     ..#
        ...     #..
        ...     ...
        ...
        ... ''')
        >>> example_grid
        Grid({(1, -1): NodeState.INFECTED, (-1, 0): NodeState.INFECTED})

    Then, the middle of the infinite grid looks like this, with the virus carrier's position marked
    with `[ ]`:

        >>> run = run_virus(example_grid, bursts=70)
        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · · · · # · ·
        · · #[·]· · ·  (↑)
        · · · · · · ·
        · · · · · · ·

    The virus carrier is on a **clean** node, so it turns **left**, infects the node, and moves
    left:

        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · · · · # · ·
        · ·[#]# · · ·  (←)
        · · · · · · ·
        · · · · · · ·

    The virus carrier is on an **infected** node, so it turns **right**, **cleans** the node, and
    moves up:

        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · ·[·]· # · ·  (↑)
        · · · # · · ·
        · · · · · · ·
        · · · · · · ·

    Four times in a row, the virus carrier finds a **clean**, **infects** it, turns **left**, and
    moves forward, ending in the same place and still facing up:

        >>> from itertools import islice
        >>> print(last(islice(run, 4)))
        · · · · · · · ·
        · · · · · · · ·
        · · #[#]· # · ·  (↑)
        · · # # # · · ·
        · · · · · · · ·
        · · · · · · · ·

    Now on the same node as before, it sees an infection, which causes it to turn **right**,
    **clean** the node, and move forward:

        >>> print(state_7 := next(run))
        · · · · · · · ·
        · · · · · · · ·
        · · # ·[·]# · ·  (→)
        · · # # # · · ·
        · · · · · · · ·
        · · · · · · · ·

    After the above actions, a total of `7` bursts of activity had taken place. Of them, `5` bursts
    of activity caused an infection:

        >>> state_7.infections, state_7.bursts
        (5, 7)

    After a total of `70`, the grid looks like this:

        >>> print(state_70 := last(run))
        · · · · · · · · · · ·
        · · · · · · · · · · ·
        · · · · · # # · · · ·
        · · · · # · · # · · ·
        · · · # · · · · # · ·
        · · # · #[·]· · # · ·  (↑)
        · · # · # · · # · · ·
        · · · · · # # · · · ·
        · · · · · · · · · · ·
        · · · · · · · · · · ·

    By this time, `41` bursts of activity caused an infection (though most of those nodes have since
    been cleaned):

        >>> state_70.infections, state_70.bursts
        (41, 70)

    After a total of `10000` bursts of activity, `5587` bursts will have caused an infection.

        >>> part_1(example_grid)
        part 1: after 10000 bursts, 5587 nodes got infected
        5587

    Given your actual map, after `10000` bursts of activity, **how many bursts cause a node to
    become infected**? (Do not count nodes that begin infected.)
    """

    last_state = last(run_virus(grid, bursts))
    print(f"part 1: after {last_state.bursts} bursts, {last_state.infections} nodes got infected")
    return last_state.infections
def part_2(grid: 'Grid', bursts: int = 10_000_000) -> int:
    """
    As you go to remove the virus from the infected nodes, it **evolves** to resist your attempt.

    Now, before it infects a clean node, it will **weaken** it to disable your defenses. If it
    encounters an infected node, it will instead **flag** the node to be cleaned in the future. So:

      - **Clean** nodes become **weakened**.
      - **Weakened** nodes become **infected**.
      - **Infected** nodes become **flagged**.
      - **Flagged** nodes become **clean**.

    Every node is always in exactly one of the above states.

    The virus carrier still functions in a similar way, but now uses the following logic during its
    bursts of action:

      - Decide which way to turn based on the current node:
        - If it is **clean**, it turns **left**.
        - If it is **weakened**, it does **not** turn, and will continue moving in the same dir.
        - If it is **infected**, it turns **right**.
        - If it is **flagged**, it **reverses** direction, and will go back the way it came.
      - Modify the state of the **current node**, as described above.
      - The virus carrier moves **forward** one node in the direction it is facing.

    Start with the same map and still with the virus carrier starting in the middle and facing
    **up**.

    Using the same initial state as the previous example, and drawing **weakened** as `o` and
    **flagged** as `f`, the middle of the infinite grid looks like this, with the virus carrier's
    position again marked with `[ ]`:

        >>> example_grid = Grid.from_file('data/22-example.txt')
        >>> run = run_virus(example_grid, bursts=100, extended_states=True)
        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · · · · # · ·
        · · #[·]· · ·  (↑)
        · · · · · · ·
        · · · · · · ·

    This is the same as before, since no initial nodes are **weakened** or **flagged**. The virus
    carrier is on a clean node, so it still turns left, instead **weakens** the node, and moves
    left:

        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · · · · # · ·
        · ·[#]o · · ·  (←)
        · · · · · · ·
        · · · · · · ·

    The virus carrier is on an infected node, so it still turns right, instead **flags** the node,
    and moves up:

        >>> print(next(run))
        · · · · · · ·
        · · · · · · ·
        · ·[·]· # · ·  (↑)
        · · f o · · ·
        · · · · · · ·
        · · · · · · ·

    This process repeats three more times, ending on the previously-flagged node and facing right:

        >>> from itertools import islice
        >>> print(last(islice(run, 3)))
        · · · · · · · ·
        · · · · · · · ·
        · · o o · # · ·
        · · o[f]o · · ·  (→)
        · · · · · · · ·
        · · · · · · · ·

    Finding a flagged node, it reverses direction and **cleans** the node:

        >>> print(next(run))
        · · · · · · · ·
        · · · · · · · ·
        · · o o · # · ·
        · ·[o]· o · · ·  (←)
        · · · · · · · ·
        · · · · · · · ·

    The **weakened** node becomes infected, and it continues in the same direction:

        >>> print(next(run))
        · · · · · · · · ·
        · · · · · · · · ·
        · · · o o · # · ·
        · ·[·]# · o · · ·  (←)
        · · · · · · · · ·
        · · · · · · · · ·

    Of the first 100 bursts, 26 will result in infection:

        >>> state_100 = last(run)
        >>> state_100.infections, state_100.bursts
        (26, 100)

    Unfortunately, another feature of this evolved virus is speed; of the first `10_000_000` bursts,
    `2511944` will result in infection:

        >>> part_2(example_grid)
        part 2: after 10000000 bursts, 2511944 nodes got infected
        2511944

    Given your actual map, after `10_000_000` bursts of activity, **how many bursts cause a node to
    become infected**? (Do not count nodes that begin infected.)
    """

    last_state = last(run_virus(grid, bursts, extended_states=True))
    print(f"part 2: after {last_state.bursts} bursts, {last_state.infections} nodes got infected")
    return last_state.infections
def part_1(instructions: Iterable['Instruction']) -> int:
    """
    You receive a signal directly from the CPU. Because of your recent assistance with jump
    instructions, it would like you to compute the result of a series of unusual register
    instructions.

    Each instruction consists of several parts: the register to modify, whether to increase or
    decrease that register's value, the amount by which to increase or decrease it, and a condition.
    If the condition fails, skip the instruction without modifying the register. The registers all
    start at `0`. The instructions look like this:

        >>> example_instructions = instructions_from_text('''
        ...     b inc 5 if a > 1
        ...     a inc 1 if b < 5
        ...     c dec -10 if a >= 1
        ...     c inc -20 if c == 10
        ... ''')
        >>> example_instructions  # doctest: +NORMALIZE_WHITESPACE
        [Instruction('b', 'inc', 5, Condition('a', '>', 1)),
         Instruction('a', 'inc', 1, Condition('b', '<', 5)),
         Instruction('c', 'dec', -10, Condition('a', '>=', 1)),
         Instruction('c', 'inc', -20, Condition('c', '==', 10))]

    These instructions would be processed as follows:

        >>> run = run_instructions(example_instructions)

      - Because `a` starts at `0`, it is not greater than `1`, and so `b` is not modified:

        >>> next(run)
        RunState(head=0, cond_eval=False, registers={})

      - `a` is increased by `1` (to `1`) because `b` is less than `5` (it is `0`):

        >>> next(run)
        RunState(head=1, cond_eval=True, registers={'a': 1})

      - `c` is decreased by `-10` (to `10`) because `a` is now greater than or equal to `1`
        (it is `1`):

        >>> next(run)
        RunState(head=2, cond_eval=True, registers={'a': 1, 'c': 10})

      - `c` is increased by `-20` (to `-10`) because `c` is equal to `10`:

        >>> next(run)
        RunState(head=3, cond_eval=True, registers={'a': 1, 'c': -10})

    After this process, the largest value in any register is `1`.

    You might also encounter `<=` (less than or equal to) or `!=` (not equal to). However, the CPU
    doesn't have the bandwidth to tell you what all the registers are named, and leaves that to you
    to determine.

    **What is the largest value in any register** after completing the instructions in your puzzle
    input?

        >>> part_1(example_instructions)
        part 1: largest value in registers is a=1
        1
    """

    final_registers = last(run_instructions(instructions)).registers
    max_value, max_reg = max(
        (val, reg) for reg, val in final_registers.items())

    print(f"part 1: largest value in registers is {max_reg}={max_value}")
    return max_value