Beispiel #1
0
 def shortest_path(self, start_at: str = '0') -> tuple[int, Path]:
     distances = self.calculate_distances()
     result_path, distance = mink(
         self.paths(start_at),
         key=lambda path: sum(distances[(a, b)]
                              for a, b in slidingw(path, 2)))
     return distance, result_path
Beispiel #2
0
 def shortest_closed_path(self, start_at: str = '0') -> tuple[int, Path]:
     distances = self.calculate_distances()
     result_path, (distance, _) = mink(
         self.closed_paths(start_at),
         # path is included in key in order to make the calculation deterministic
         # because closed path can be reversed (0-1-2-0 and 0-2-1-0)
         key=lambda path:
         (sum(distances[(a, b)] for a, b in slidingw(path, 2)), path))
     return distance, result_path
def part_2(polymer: str) -> int:
    """
    Time to improve the polymer.

    One of the unit types is causing problems; it's preventing the polymer from collapsing as much
    as it should. Your goal is to figure out which unit type is causing the most problems, remove
    all instances of it (regardless of polarity), fully react the remaining polymer, and measure its
    length.

    For example, again using the polymer `dabAcCaCBAcCcaDA` from above:

        >>> example = 'dabAcCaCBAcCcaDA'

      - Removing all `A`/`a` units produces `dbcCCBcCcD`.
        Fully reacting this polymer produces `dbCBcD`, which has length `6`.

        >>> react(without_units(example, 'A'))
        'dbCBcD'

      - Removing all `B`/`b` units produces `daAcCaCAcCcaDA`.
        Fully reacting this polymer produces `daCAcaDA`, which has length `8`.

        >>> react(without_units(example, 'B'))
        'daCAcaDA'

      - Removing all `C`/`c` units produces `dabAaBAaDA`.
        Fully reacting this polymer produces daDA, which has length `4`.

        >>> react(without_units(example, 'C'))
        'daDA'

      - Removing all `D`/`d` units produces `abAcCaCBAcCcaA`.
        Fully reacting this polymer produces abCBAc, which has length `6`.

        >>> react(without_units(example, 'D'))
        'abCBAc'

    In this example, removing all `C`/`c` units was best, producing the answer `4`.

    **What is the length of the shortest polymer you can produce** by removing all units of exactly
    one type and fully reacting the result?

        >>> part_2(example)
        part 2: best to remove 'C'/'c' -> polymer length after reactions is then 4
        4
    """

    units = sorted(set(polymer.lower()))
    removed, polymer_length = mink(
        units, key=lambda rc: len(react(without_units(polymer, rc))))

    print(f"part 2: best to remove {removed.upper()!r}/{removed!r} "
          f"-> polymer length after reactions is then {polymer_length}")
    return polymer_length
Beispiel #4
0
def ideal_configuration(weights: list[int],
                        containers_count: int) -> tuple[Config, int]:
    weights = sorted(weights, reverse=True)

    def generate_configs_with_shortest_c1():
        prev_config = None
        for config in generate_configs(weights, containers_count):
            # pylint: disable=unsubscriptable-object
            if prev_config and len(prev_config[0]) < len(config[0]):
                return
            yield config
            prev_config = config

    best_config, quantum_entanglement = mink(tqdm(
        generate_configs_with_shortest_c1(), delay=1.0, unit=" configs"),
                                             key=lambda c: math.prod(c[0]))
    return best_config, quantum_entanglement
Beispiel #5
0
def part_1(wire1: Wire, wire2: Wire):
    closest_xs, distance = mink(intersections(wire1, wire2), key=md)
    print(f"part 1: closest xs is at {closest_xs}, distance={distance}")
Beispiel #6
0
def part_1(boss: 'Character', player_hp: int, shop: 'Shop') -> int:
    """
    Little Henry Case got a new video game for Christmas. It's an RPG, and he's stuck on a boss. He
    needs to know what equipment to buy at the shop. He hands you the controller.

    In this game, the player (you) and the enemy (the boss) take turns attacking. The player always
    goes first. Each attack reduces the opponent's hit points by at least `1`. The first character
    at or below 0 hit points loses.

    Damage dealt by an attacker each turn is equal to the attacker's damage score minus
    the defender's armor score. An attacker always does at least `1` damage. So, if the attacker has
    a damage score of `8`, and the defender has an armor score of `3`, the defender loses `5` hit
    points. If the defender had an armor score of `300`, the defender would still lose `1` hit
    point.

    Your damage score and armor score both start at zero. They can be increased by buying items in
    exchange for gold. You start with no items and have as much gold as you need. Your total damage
    or armor is equal to the sum of those stats from all of your items. You have **100 hit points**.

    Here is what the item shop is selling:

        >>> the_shop = Shop.from_file('data/21-shop.txt')
        >>> print(the_shop)
        Weapons:    Cost  Damage  Armor
        Dagger        8     4       0
        Shortsword   10     5       0
        Warhammer    25     6       0
        Longsword    40     7       0
        Greataxe     74     8       0
        <BLANKLINE>
        Armor:      Cost  Damage  Armor
        Leather      13     0       1
        Chainmail    31     0       2
        Splintmail   53     0       3
        Bandedmail   75     0       4
        Platemail   102     0       5
        <BLANKLINE>
        Rings:      Cost  Damage  Armor
        Damage +1    25     1       0
        Damage +2    50     2       0
        Damage +3   100     3       0
        Defense +1   20     0       1
        Defense +2   40     0       2
        Defense +3   80     0       3

    You must buy exactly one weapon; no dual-wielding. Armor is optional, but you can't use more
    than one. You can buy 0-2 rings (at most one for each hand). You must use any items you buy.
    The shop only has one of each item, so you can't buy, for example, two rings of Damage +3.

    For example, suppose you have `8` hit points, Shortsword, Splintmail and Ring of Defense +2,
    and that the boss has `12` hit points, `7` damage, and `2` armor:

        >>> example_equipment = the_shop.get_items('Shortsword', 'Splintmail', 'Defense +2')
        >>> example_equipment  # doctest: +NORMALIZE_WHITESPACE
        [Item('Shortsword', cost=10, damage=5),
         Item('Splintmail', cost=53, armor=3),
         Item('Defense +2', cost=40, armor=2)]
        >>> player = Character('player', hit_points=8)
        >>> player_equipped = player.with_equipment(example_equipment)
        >>> player_equipped
        Character('player', hit_points=8, damage=5, armor=5)
        >>> example_boss = Character('boss', hit_points=12, damage=7, armor=2)
        >>> winner = battle(player_equipped, example_boss, log=True)
        The player deals 5-2 = 3 damage; the boss goes down to 9 hit points.
        The boss deals 7-5 = 2 damage; the player goes down to 6 hit points.
        The player deals 5-2 = 3 damage; the boss goes down to 6 hit points.
        The boss deals 7-5 = 2 damage; the player goes down to 4 hit points.
        The player deals 5-2 = 3 damage; the boss goes down to 3 hit points.
        The boss deals 7-5 = 2 damage; the player goes down to 2 hit points.
        The player deals 5-2 = 3 damage; the boss goes down to 0 hit points.

    In this scenario, the player wins! (Barely.)

        >>> winner.name
        'player'

    If the player had only Longsword and Ring of Damage +1, they would win as well:

        >>> example_equipment_2 = the_shop.get_items('Longsword', 'Damage +1')
        >>> example_equipment_2
        [Item('Longsword', cost=40, damage=7), Item('Damage +1', cost=25, damage=1)]
        >>> example_player_equipped_2 = player.with_equipment(example_equipment_2)
        >>> example_player_equipped_2
        Character('player', hit_points=8, damage=8, armor=0)
        >>> winner = battle(example_player_equipped_2, example_boss, log=True)
        The player deals 8-2 = 6 damage; the boss goes down to 6 hit points.
        The boss deals 7-0 = 7 damage; the player goes down to 1 hit point.
        The player deals 8-2 = 6 damage; the boss goes down to 0 hit points.

    But with cheaper equipment!

        >>> total_cost(example_equipment)
        103
        >>> total_cost(example_equipment_2)
        65

    You have **100 hit points**. The boss's actual stats are in your puzzle input.
    What is **the least amount of gold you can spend** and still win the fight?

        >>> part_1(example_boss, player_hp=8, shop=the_shop)
        part 1: you can beat the boss by spending only 65 gold: Longsword, Damage +1
        65
    """

    winning_builds = (build for build in shop.generate_builds()
                      if is_beating_boss(build, boss, player_hp))
    cheapest_build, cost = mink(winning_builds, key=total_cost)

    build_str = ", ".join(item.name for item in cheapest_build)
    print(
        f"part 1: you can beat the boss by spending only {cost} gold: {build_str}"
    )
    return cost