~dricottone/riddler-230106

ref: 0216807bec87546055061031b3ebb14870072800 riddler-230106/classic/main.py -rwxr-xr-x 3.4 KiB
0216807bDominic Ricottone Solution to classic riddler 1 year, 10 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python3

from typing import Generator, NamedTuple

HOURS_TO_PRODUCE_ASSEMBLER = 6 * 24  # 6 days * 24 hours/day = 144 hours

# equivalent constant, but this name is more legible in certain contexts
FIGHTERS_PRODUCED_IN_ONE_UNIT = HOURS_TO_PRODUCE_ASSEMBLER

class Strategy(NamedTuple):
    available_hours: int
    produced_fighters: int

class MaxStrategy(NamedTuple):
    available_hours: int
    produced_fighters: int
    produced_assemblers: int

def assembler_schedule(available_hours) -> dict[int, Strategy]:
    """A schedule of quantities of assemblers that are possible within
    `available_hours`.

    Index the schedule by a target quantity of assemblers. This provides the
    optimal strategy to produce that quantity. A strategy is represented by a
    tuple of available hours and number of fighters produced incidentally.

    The first assembler is given; there is no strategy for 0 assemblers.

    If there is no strategy to produce a quantity given `available_hours`, the
    schedule will not be indexable by that quantity.
    """
    # initialize the 'do nothing' strategy
    schedule = { 1: Strategy(available_hours, 0) }

    while available_hours >= HOURS_TO_PRODUCE_ASSEMBLER:
        # consume 6 days
        available_hours -= HOURS_TO_PRODUCE_ASSEMBLER

        # find the maximal number of assemblers at this hour
        assemblers = max(schedule.keys())

        # with N assemblers, for n in 1..N, n will produce assemblers
        for i in range(1, assemblers + 1):
            produced_assemblers = assemblers + i
            produced_fighters = (assemblers - i) * HOURS_TO_PRODUCE_ASSEMBLER
            schedule[produced_assemblers] = Strategy(available_hours, produced_fighters)

    return schedule

def evaluate_fighters(strategy: Strategy, assemblers: int) -> int:
    """Helper function to calculate final production of fighters."""
    return strategy.produced_fighters + (assemblers * strategy.available_hours)

def evaluate_schedule(schedule: dict[int, Strategy]) -> Generator[tuple[int, int, Strategy], None, None]:
    """For each strategy, determine how many fighters would be produced by
    setting all assemblers to produce fighters for the availabe hours.
    """
    for assemblers in schedule.keys():
        fighters = evaluate_fighters(schedule[assemblers], assemblers)
        yield (fighters, assemblers, schedule[assemblers])

def max_schedule(schedule: dict[int, Strategy]) -> MaxStrategy:
    """Find the maximal strategy within a schedule.

    A tuple of hours remaining, fighters proeduced, and assemblers produced.
    """
    # initialize with the 'do nothing' strategy
    return max(tuple(evaluate_schedule(schedule)), key=lambda x: x[0])

def dump():
    schedule = assembler_schedule(100 * 24)
    print("FinalFighters,ProducedAssemblers,ProducedFighters,UnconsumedHours")
    for fighters, assemblers, strategy in evaluate_schedule(schedule):
        print(f"{fighters}\t{assemblers}\t{strategy.produced_fighters}\t{strategy.available_hours}")

def main():
    schedule = assembler_schedule(100 * 24)
    maximum = max_schedule(schedule)
    print(f"Built {maximum[0]} fighters with {maximum[1]} assemblers")
    print(f" * {maximum[2].produced_fighters} fighters were built concurrent to the assemblers")
    print(f" * {maximum[0] - maximum[2].produced_fighters} fighters were built in the unconsumed {maximum[2].available_hours} hours")

if __name__ == "__main__":
    main()