def part_2(ingredients: list['Ingredient'], calories: int = 500) -> int: """ Your cookie recipe becomes wildly popular! Someone asks if you can make another recipe that has exactly `500` calories per cookie (so they can use it as a meal replacement). Keep the rest of your award-winning process the same (`100` teaspoons, same ingredients, same scoring system). For example, given the ingredients above, if you had instead selected `40` teaspoons of butterscotch and `60` teaspoons of cinnamon (which still adds to `100`), the total calorie count would be `40*8 + 60*3 = 500`. The total score would go down, though: only `57600000`, the best you can do in such trying circumstances. >>> butterscotch, cinnamon = ingredients_from_file('data/15-example.txt') >>> recipe_score({butterscotch: 40, cinnamon: 60}, required_calories=500) 57600000 >>> recipe_score({butterscotch: 44, cinnamon: 56}, required_calories=500) -1 Given the ingredients in your kitchen and their properties, what is the **total score** of the highest-scoring cookie you can make with a calorie total of `500`? >>> part_2([butterscotch, cinnamon]) part 2: best recipe with 500 calories (40 Butterscotch, 60 Cinnamon) has score 57600000 57600000 """ best_recipe, score = maxk( generate_recipes(ingredients, 100), key=lambda r: recipe_score(r, required_calories=calories)) print( f"part 2: best recipe with {calories} calories ({recipe_str(best_recipe)}) " f"has score {score}") return score
def most_distant_scanners(self) -> tuple[tuple[Scanner, Scanner], int]: assert len(self.fixed_readings) >= 2 def manhattan_distance(scanner_pair: tuple[Scanner, Scanner]) -> int: return sum(abs(v) for v in (scanner_pair[0].pos - scanner_pair[1].pos)) scanner_pairs = ((s1, s2) for s1, s2 in itertools.combinations(self.scanners, 2)) return maxk(scanner_pairs, key=manhattan_distance)
def part_2(directions: Iterable[str]) -> int: """ **How many steps away** is the **furthest** he ever got from his starting position? >>> part_2(['n', 'ne', 'n', 'ne', 'se', 's', 'nw', 'sw', 'sw']) part 2: max distance was 4 4 """ origin = (0, 0) _, max_distance = maxk(steps(origin, directions), lambda pos: distance(origin, pos)) print(f"part 2: max distance was {max_distance}") return max_distance
def part_2(boss: 'Character', player_hp: int, shop: 'Shop') -> int: """ Turns out the shopkeeper is working with the boss, and can persuade you to buy whatever items he wants. The other rules still apply, and he still only has one of each item. >>> (example_boss := Character.from_file("boss", 'data/21-example-boss.txt')) Character('boss', hit_points=12, damage=7, armor=2) >>> the_shop = Shop.from_file('data/21-shop.txt') >>> expensive_equipment = the_shop.get_items('Dagger', 'Damage +3', 'Defense +3') >>> expensive_equipment # doctest: +NORMALIZE_WHITESPACE [Item('Dagger', cost=8, damage=4), Item('Damage +3', cost=100, damage=3), Item('Defense +3', cost=80, armor=3)] >>> (player := Character('player', hit_points=8).with_equipment(expensive_equipment)) Character('player', hit_points=8, damage=7, armor=3) >>> winner = battle(player, example_boss, log=True) The player deals 7-2 = 5 damage; the boss goes down to 7 hit points. The boss deals 7-3 = 4 damage; the player goes down to 4 hit points. The player deals 7-2 = 5 damage; the boss goes down to 2 hit points. The boss deals 7-3 = 4 damage; the player goes down to 0 hit points. >>> winner.name 'boss' What is the **most** amount of gold you can spend and still lose the fight? >>> part_2(example_boss, player_hp=8, shop=the_shop) part 2: you can spend 188 gold and still lose the fight: Dagger, Damage +3, Defense +3 188 """ losing_builds = (build for build in shop.generate_builds() if not is_beating_boss(build, boss, player_hp)) most_expensive_build, cost = maxk(losing_builds, key=total_cost) build_str = ", ".join(item.name for item in most_expensive_build) print( f"part 2: you can spend {cost} gold and still lose the fight: {build_str}" ) return cost
def max_square(serial: int, square_size: int | range = 3, grid_size: int = 300) -> tuple[tuple[int, int, int], int]: if isinstance(square_size, range): square_size_range = square_size elif isinstance(square_size, int): square_size_range = range(square_size, square_size + 1) else: raise TypeError(type(square_size)) squares = ((x, y, size) for size in square_size_range for x in range(1, grid_size - size + 2) for y in range(1, grid_size - size + 2)) squares_progress = tqdm(squares, desc="finding square", total=sum((grid_size - size + 1)**2 for size in square_size_range), unit=" squares", unit_scale=True, delay=0.5) return maxk(squares_progress, key=lambda square: square_sum(*square, serial))
def points_race_winner(reindeer: list[Reindeer], seconds: int) -> tuple[str, int]: race_results = points_race(reindeer, seconds) return maxk(race_results, key=lambda r: race_results[r])
def part_1(ingredients: list['Ingredient']) -> int: """ Today, you set out on the task of perfecting your milk-dunking cookie recipe. All you have to do is find the right balance of ingredients. Your recipe leaves room for exactly `100` teaspoons of ingredients. You make a list of the **remaining ingredients you could use to finish the recipe** (your puzzle input) and their **properties per teaspoon**: - `capacity` (how well it helps the cookie absorb milk) - `durability` (how well it keeps the cookie intact when full of milk) - `flavor` (how tasty it makes the cookie) - `texture` (how it improves the feel of the cookie) - `calories` (how many calories it adds to the cookie) You can only measure ingredients in whole-teaspoon amounts accurately, and you have to be accurate so you can reproduce your results in the future. The **total score** of a cookie can be found by adding up each of the properties (negative totals become `0`) and then multiplying together everything except calories. For instance, suppose you have these two ingredients: >>> butterscotch, cinnamon = ingredients_from_text(''' ... Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8 ... Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3 ... ''') >>> butterscotch Ingredient('Butterscotch', capacity=-1, durability=-2, flavor=6, texture=3, calories=8) >>> cinnamon Ingredient('Cinnamon', capacity=2, durability=3, flavor=-2, texture=-1, calories=3) Then, choosing to use `44` teaspoons of butterscotch and `56` teaspoons of cinnamon (because the amounts of each ingredient must add up to `100`) would result in a cookie with the following properties: - A `capacity` of `44*-1 + 56* 2 = 68` - A `durability` of `44*-2 + 56* 3 = 80` - A `flavor` of `44* 6 + 56*-2 = 152` - A `texture` of `44* 3 + 56*-1 = 76` Multiplying these together (`68 * 80 * 152 * 76`, ignoring `calories` for now) results in a total score of `62842880`, which happens to be the best score possible given these ingredients. >>> recipe_score({butterscotch: 44, cinnamon: 56}) 62842880 If any properties had produced a negative total, it would have instead become zero, causing the whole score to multiply to zero. >>> recipe_score({butterscotch: 66, cinnamon: 34}) 0 Given the ingredients in your kitchen and their properties, what is the **total score** of the highest-scoring cookie you can make? >>> part_1([butterscotch, cinnamon]) part 1: best recipe (44 Butterscotch, 56 Cinnamon) has score 62842880 62842880 """ best_recipe, score = maxk(generate_recipes(ingredients, 100), key=recipe_score) print(f"part 1: best recipe ({recipe_str(best_recipe)}) has score {score}") return score
def part_1(seat_codes: Iterable[str]) -> int: """ This airline uses binary space partitioning to seat people. A seat might be specified like `FBFBBFFRLR`, where `F` means "front", `B` means "back", `L` means "left", and `R` means "right". The first 7 characters will either be `F` or `B`; these specify exactly one of the *128 rows* on the plane (numbered 0 through 127). Each letter tells you which half of a region the given seat is in. Start with the whole list of rows; the first letter indicates whether the seat is in the *front* (0 through 63) or the *back* (64 through 127). The next letter indicates which half of that region the seat is in, and so on until you're left with exactly one row. For example, consider just the first seven characters of `FBFBBFFRLR`: - Start by considering the whole range, rows `0` through `127`. - `F` means to take the lower half, keeping rows `0` through `63`. - `B` means to take the upper half, keeping rows `32` through `63`. - `F` means to take the lower half, keeping rows `32` through `47`. - `B` means to take the upper half, keeping rows `40` through `47`. - `B` keeps rows `44` through `47`. - `F` keeps rows `44` through `45`. - The final `F` keeps the lower of the two, row `44`. >>> row_number('FBFBBFF') 44 The last three characters will be either `L` or `R`; these specify exactly one of the 8 columns of seats on the plane (numbered `0` through `7`). The same process as above proceeds again, this time with only three steps. `L` means to keep the *lower half*, while `R` means to keep the *upper half*. For example, consider just the last 3 characters of `FBFBBFFRLR`: - Start by considering the whole range, columns `0` through `7`. - `R` means to take the upper half, keeping columns `4` through `7`. - `L` means to take the lower half, keeping columns `4` through `5`. - The final `R` keeps the upper of the two, column `5`. >>> column_number('RLR') 5 So, decoding `FBFBBFFRLR` reveals that it is the seat at *row `44`, column `5`*. >>> seat_coordinates('FBFBBFFRLR') (44, 5) Every seat also has a unique *seat ID*: multiply the row by 8, then add the column. In this example, the seat has ID `44 * 8 + 5 = 357`. >>> seat_id('FBFBBFFRLR') 357 Here are some other boarding passes: >>> passes = ['BFFFBBFRRR', 'FFFBBBFRRR', 'BBFFBBFRLL'] >>> seat_coordinates(passes[0]), seat_id(passes[0]) ((70, 7), 567) >>> seat_coordinates(passes[1]), seat_id(passes[1]) ((14, 7), 119) >>> seat_coordinates(passes[2]), seat_id(passes[2]) ((102, 4), 820) As a sanity check, look through your list of boarding passes. *What is the highest seat ID on a boarding pass?* >>> part_1(passes) part 1: boarding pass 'BBFFBBFRLL' has the highest seat ID: 820 820 """ max_seat_code, max_seat_id = maxk(seat_codes, key=seat_id) print( f"part 1: boarding pass {max_seat_code!r} has the highest seat ID: {max_seat_id}" ) return max_seat_id