1import math
2
3
4class Candy:
5 """Represent a Halloween in Pripyat candy."""
6
7 def __init__(self, mass, uranium):
8 """Initializator."""
9 self._mass = mass
10 self._uranium = uranium
11
12 def get_uranium_quantity(self):
13 """Get absolute content of uranium."""
14 return self._mass * self._uranium
15
16 def get_mass(self):
17 """Get the mass of the candy."""
18 return self._mass
19
20
21class Person:
22 """Represent a Halloween in Pripyat person."""
23
24 def __init__(self, position):
25 """Initializator."""
26 self._position = position
27
28 def get_position(self):
29 """Get the current position of a person."""
30 return self._position
31
32 def set_position(self, position):
33 """Set the current position of a person."""
34 self._position = position
35
36
37class Kid(Person):
38 """Represent a Halloween in Pripyat kid."""
39
40 CRITICAL_URANIUM = 20
41
42 def __init__(self, position, initiative):
43 """Initializator."""
44 super().__init__(position)
45 self._candies = set()
46 self._initiative = initiative
47
48 def get_initiative(self):
49 """Get the initiative value of the kid."""
50 return self._initiative
51
52 def add_candy(self, candy):
53 """Add a new candy to the set of owned candies."""
54 self._candies.add(candy)
55
56 def is_critical(self):
57 """Check if the uranium mass in owned candies is critical."""
58 total = sum([candy.get_uranium_quantity() for candy in self._candies])
59 return total > self.CRITICAL_URANIUM
60
61
62class Host(Person):
63 """Represent a Halloween in Pripyat host."""
64
65 def __init__(self, position, candies):
66 """Initializator."""
67 super().__init__(position)
68 self._candies = {Candy(*candy) for candy in candies}
69
70 def remove_candy(self, fun):
71 """Remove a candy from the set of owned candies."""
72 if not self._candies:
73 return None
74 candy = fun(self._candies)
75 self._candies.remove(candy)
76 return candy
77
78
79class Visit:
80 """A single visit between a kid and a host."""
81
82 def __init__(self, kid, host, turn_index):
83 """Initializator."""
84 self.kid = kid
85 self.host = host
86 self.turn_index = turn_index
87
88
89class FluxCapacitor:
90 """Represent a Halloween in Pripyat simulator."""
91
92 def __init__(self, participants):
93 """Initializator."""
94 self._kids = list(filter(lambda x: type(x) is Kid, participants))
95 self._hosts = list(filter(lambda x: type(x) is Host, participants))
96 self._visits = self._init_visits()
97
98 @staticmethod
99 def _distance(kid, host):
100 """Calculate dinstance between a kid and a host."""
101 return math.dist(kid.get_position(), host.get_position())
102
103 @staticmethod
104 def _select_candy(candies):
105 """Return a function to select the candy with highest mass from a set of candies."""
106 return max(candies, key=lambda candy: candy.get_mass())
107
108 @classmethod
109 def _get_closest_host_of_kid(cls, kid, hosts):
110 """Get the host that is closest to a particular kid."""
111 return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position()))
112
113 def _init_visits(self):
114 """Calculate all visits for the night."""
115 visits = []
116 for kid in self._kids:
117 hosts = self._hosts[:]
118 turn_index = 0
119 while hosts:
120 host = self._get_closest_host_of_kid(kid, hosts)
121 hosts.remove(host)
122 visits.append(Visit(kid, host, turn_index))
123 turn_index += 1
124 kid.set_position(host.get_position())
125 return visits
126
127 def _pop_visits(self):
128 """Remove next visits from all-visits set and return them."""
129 popped_visits = []
130 next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index
131 for visit in self._visits:
132 if visit.turn_index == next_turn:
133 popped_visits.append(visit)
134 self._visits = [visit for visit in self._visits if visit not in popped_visits]
135 return popped_visits
136
137 def _make_a_visit(self):
138 """Make a single visit for the night and return exchanges for them."""
139 if not self._visits:
140 return None
141 visits = self._pop_visits()
142 exchanges = {}
143 for visit in visits:
144 exchanges.setdefault(visit.host, []).append(visit.kid)
145 return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()}
146
147 def _exchange_candy(self, exchanges):
148 """Make a candy exchange."""
149 deads = set()
150 for host, kids in exchanges.items():
151 for kid in kids:
152 candy = host.remove_candy(self._select_candy)
153 if candy:
154 kid.add_candy(candy)
155 if kid.is_critical():
156 deads.add(kid)
157 return deads or None
158
159 def get_victim(self):
160 """Get the first victim of the night."""
161 while True:
162 if not (exchanges:=self._make_a_visit()):
163 return None
164 if victim:=self._exchange_candy(exchanges):
165 return victim
............
----------------------------------------------------------------------
Ran 12 tests in 0.001s
OK
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
t | 4 | class Candy1: | t | 4 | class Candy: |
5 | """Represent a Halloween in Pripyat candy.""" | 5 | """Represent a Halloween in Pripyat candy.""" | ||
6 | 6 | ||||
7 | def __init__(self, mass, uranium): | 7 | def __init__(self, mass, uranium): | ||
8 | """Initializator.""" | 8 | """Initializator.""" | ||
9 | self._mass = mass | 9 | self._mass = mass | ||
10 | self._uranium = uranium | 10 | self._uranium = uranium | ||
11 | 11 | ||||
12 | def get_uranium_quantity(self): | 12 | def get_uranium_quantity(self): | ||
13 | """Get absolute content of uranium.""" | 13 | """Get absolute content of uranium.""" | ||
14 | return self._mass * self._uranium | 14 | return self._mass * self._uranium | ||
15 | 15 | ||||
16 | def get_mass(self): | 16 | def get_mass(self): | ||
17 | """Get the mass of the candy.""" | 17 | """Get the mass of the candy.""" | ||
18 | return self._mass | 18 | return self._mass | ||
19 | 19 | ||||
20 | 20 | ||||
21 | class Person: | 21 | class Person: | ||
22 | """Represent a Halloween in Pripyat person.""" | 22 | """Represent a Halloween in Pripyat person.""" | ||
23 | 23 | ||||
24 | def __init__(self, position): | 24 | def __init__(self, position): | ||
25 | """Initializator.""" | 25 | """Initializator.""" | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | def get_position(self): | 28 | def get_position(self): | ||
29 | """Get the current position of a person.""" | 29 | """Get the current position of a person.""" | ||
30 | return self._position | 30 | return self._position | ||
31 | 31 | ||||
32 | def set_position(self, position): | 32 | def set_position(self, position): | ||
33 | """Set the current position of a person.""" | 33 | """Set the current position of a person.""" | ||
34 | self._position = position | 34 | self._position = position | ||
35 | 35 | ||||
36 | 36 | ||||
37 | class Kid(Person): | 37 | class Kid(Person): | ||
38 | """Represent a Halloween in Pripyat kid.""" | 38 | """Represent a Halloween in Pripyat kid.""" | ||
39 | 39 | ||||
40 | CRITICAL_URANIUM = 20 | 40 | CRITICAL_URANIUM = 20 | ||
41 | 41 | ||||
42 | def __init__(self, position, initiative): | 42 | def __init__(self, position, initiative): | ||
43 | """Initializator.""" | 43 | """Initializator.""" | ||
44 | super().__init__(position) | 44 | super().__init__(position) | ||
45 | self._candies = set() | 45 | self._candies = set() | ||
46 | self._initiative = initiative | 46 | self._initiative = initiative | ||
47 | 47 | ||||
48 | def get_initiative(self): | 48 | def get_initiative(self): | ||
49 | """Get the initiative value of the kid.""" | 49 | """Get the initiative value of the kid.""" | ||
50 | return self._initiative | 50 | return self._initiative | ||
51 | 51 | ||||
52 | def add_candy(self, candy): | 52 | def add_candy(self, candy): | ||
53 | """Add a new candy to the set of owned candies.""" | 53 | """Add a new candy to the set of owned candies.""" | ||
54 | self._candies.add(candy) | 54 | self._candies.add(candy) | ||
55 | 55 | ||||
56 | def is_critical(self): | 56 | def is_critical(self): | ||
57 | """Check if the uranium mass in owned candies is critical.""" | 57 | """Check if the uranium mass in owned candies is critical.""" | ||
58 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 58 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
59 | return total > self.CRITICAL_URANIUM | 59 | return total > self.CRITICAL_URANIUM | ||
60 | 60 | ||||
61 | 61 | ||||
62 | class Host(Person): | 62 | class Host(Person): | ||
63 | """Represent a Halloween in Pripyat host.""" | 63 | """Represent a Halloween in Pripyat host.""" | ||
64 | 64 | ||||
65 | def __init__(self, position, candies): | 65 | def __init__(self, position, candies): | ||
66 | """Initializator.""" | 66 | """Initializator.""" | ||
67 | super().__init__(position) | 67 | super().__init__(position) | ||
68 | self._candies = {Candy(*candy) for candy in candies} | 68 | self._candies = {Candy(*candy) for candy in candies} | ||
69 | 69 | ||||
70 | def remove_candy(self, fun): | 70 | def remove_candy(self, fun): | ||
71 | """Remove a candy from the set of owned candies.""" | 71 | """Remove a candy from the set of owned candies.""" | ||
72 | if not self._candies: | 72 | if not self._candies: | ||
73 | return None | 73 | return None | ||
74 | candy = fun(self._candies) | 74 | candy = fun(self._candies) | ||
75 | self._candies.remove(candy) | 75 | self._candies.remove(candy) | ||
76 | return candy | 76 | return candy | ||
77 | 77 | ||||
78 | 78 | ||||
79 | class Visit: | 79 | class Visit: | ||
80 | """A single visit between a kid and a host.""" | 80 | """A single visit between a kid and a host.""" | ||
81 | 81 | ||||
82 | def __init__(self, kid, host, turn_index): | 82 | def __init__(self, kid, host, turn_index): | ||
83 | """Initializator.""" | 83 | """Initializator.""" | ||
84 | self.kid = kid | 84 | self.kid = kid | ||
85 | self.host = host | 85 | self.host = host | ||
86 | self.turn_index = turn_index | 86 | self.turn_index = turn_index | ||
87 | 87 | ||||
88 | 88 | ||||
89 | class FluxCapacitor: | 89 | class FluxCapacitor: | ||
90 | """Represent a Halloween in Pripyat simulator.""" | 90 | """Represent a Halloween in Pripyat simulator.""" | ||
91 | 91 | ||||
92 | def __init__(self, participants): | 92 | def __init__(self, participants): | ||
93 | """Initializator.""" | 93 | """Initializator.""" | ||
94 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | 94 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | ||
95 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | 95 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | ||
96 | self._visits = self._init_visits() | 96 | self._visits = self._init_visits() | ||
97 | 97 | ||||
98 | @staticmethod | 98 | @staticmethod | ||
99 | def _distance(kid, host): | 99 | def _distance(kid, host): | ||
100 | """Calculate dinstance between a kid and a host.""" | 100 | """Calculate dinstance between a kid and a host.""" | ||
101 | return math.dist(kid.get_position(), host.get_position()) | 101 | return math.dist(kid.get_position(), host.get_position()) | ||
102 | 102 | ||||
103 | @staticmethod | 103 | @staticmethod | ||
104 | def _select_candy(candies): | 104 | def _select_candy(candies): | ||
105 | """Return a function to select the candy with highest mass from a set of candies.""" | 105 | """Return a function to select the candy with highest mass from a set of candies.""" | ||
106 | return max(candies, key=lambda candy: candy.get_mass()) | 106 | return max(candies, key=lambda candy: candy.get_mass()) | ||
107 | 107 | ||||
108 | @classmethod | 108 | @classmethod | ||
109 | def _get_closest_host_of_kid(cls, kid, hosts): | 109 | def _get_closest_host_of_kid(cls, kid, hosts): | ||
110 | """Get the host that is closest to a particular kid.""" | 110 | """Get the host that is closest to a particular kid.""" | ||
111 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | 111 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||
112 | 112 | ||||
113 | def _init_visits(self): | 113 | def _init_visits(self): | ||
114 | """Calculate all visits for the night.""" | 114 | """Calculate all visits for the night.""" | ||
115 | visits = [] | 115 | visits = [] | ||
116 | for kid in self._kids: | 116 | for kid in self._kids: | ||
117 | hosts = self._hosts[:] | 117 | hosts = self._hosts[:] | ||
118 | turn_index = 0 | 118 | turn_index = 0 | ||
119 | while hosts: | 119 | while hosts: | ||
120 | host = self._get_closest_host_of_kid(kid, hosts) | 120 | host = self._get_closest_host_of_kid(kid, hosts) | ||
121 | hosts.remove(host) | 121 | hosts.remove(host) | ||
122 | visits.append(Visit(kid, host, turn_index)) | 122 | visits.append(Visit(kid, host, turn_index)) | ||
123 | turn_index += 1 | 123 | turn_index += 1 | ||
124 | kid.set_position(host.get_position()) | 124 | kid.set_position(host.get_position()) | ||
125 | return visits | 125 | return visits | ||
126 | 126 | ||||
127 | def _pop_visits(self): | 127 | def _pop_visits(self): | ||
128 | """Remove next visits from all-visits set and return them.""" | 128 | """Remove next visits from all-visits set and return them.""" | ||
129 | popped_visits = [] | 129 | popped_visits = [] | ||
130 | next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index | 130 | next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index | ||
131 | for visit in self._visits: | 131 | for visit in self._visits: | ||
132 | if visit.turn_index == next_turn: | 132 | if visit.turn_index == next_turn: | ||
133 | popped_visits.append(visit) | 133 | popped_visits.append(visit) | ||
134 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | 134 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | ||
135 | return popped_visits | 135 | return popped_visits | ||
136 | 136 | ||||
137 | def _make_a_visit(self): | 137 | def _make_a_visit(self): | ||
138 | """Make a single visit for the night and return exchanges for them.""" | 138 | """Make a single visit for the night and return exchanges for them.""" | ||
139 | if not self._visits: | 139 | if not self._visits: | ||
140 | return None | 140 | return None | ||
141 | visits = self._pop_visits() | 141 | visits = self._pop_visits() | ||
142 | exchanges = {} | 142 | exchanges = {} | ||
143 | for visit in visits: | 143 | for visit in visits: | ||
144 | exchanges.setdefault(visit.host, []).append(visit.kid) | 144 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
145 | return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()} | 145 | return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()} | ||
146 | 146 | ||||
147 | def _exchange_candy(self, exchanges): | 147 | def _exchange_candy(self, exchanges): | ||
148 | """Make a candy exchange.""" | 148 | """Make a candy exchange.""" | ||
149 | deads = set() | 149 | deads = set() | ||
150 | for host, kids in exchanges.items(): | 150 | for host, kids in exchanges.items(): | ||
151 | for kid in kids: | 151 | for kid in kids: | ||
152 | candy = host.remove_candy(self._select_candy) | 152 | candy = host.remove_candy(self._select_candy) | ||
153 | if candy: | 153 | if candy: | ||
154 | kid.add_candy(candy) | 154 | kid.add_candy(candy) | ||
155 | if kid.is_critical(): | 155 | if kid.is_critical(): | ||
156 | deads.add(kid) | 156 | deads.add(kid) | ||
157 | return deads or None | 157 | return deads or None | ||
158 | 158 | ||||
159 | def get_victim(self): | 159 | def get_victim(self): | ||
160 | """Get the first victim of the night.""" | 160 | """Get the first victim of the night.""" | ||
161 | while True: | 161 | while True: | ||
162 | if not (exchanges:=self._make_a_visit()): | 162 | if not (exchanges:=self._make_a_visit()): | ||
163 | return None | 163 | return None | ||
164 | if victim:=self._exchange_candy(exchanges): | 164 | if victim:=self._exchange_candy(exchanges): | ||
165 | return victim | 165 | return victim |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
t | 4 | class Candy: | t | 4 | class Candy1: |
5 | """Represent a Halloween in Pripyat candy.""" | 5 | """Represent a Halloween in Pripyat candy.""" | ||
6 | 6 | ||||
7 | def __init__(self, mass, uranium): | 7 | def __init__(self, mass, uranium): | ||
8 | """Initializator.""" | 8 | """Initializator.""" | ||
9 | self._mass = mass | 9 | self._mass = mass | ||
10 | self._uranium = uranium | 10 | self._uranium = uranium | ||
11 | 11 | ||||
12 | def get_uranium_quantity(self): | 12 | def get_uranium_quantity(self): | ||
13 | """Get absolute content of uranium.""" | 13 | """Get absolute content of uranium.""" | ||
14 | return self._mass * self._uranium | 14 | return self._mass * self._uranium | ||
15 | 15 | ||||
16 | def get_mass(self): | 16 | def get_mass(self): | ||
17 | """Get the mass of the candy.""" | 17 | """Get the mass of the candy.""" | ||
18 | return self._mass | 18 | return self._mass | ||
19 | 19 | ||||
20 | 20 | ||||
21 | class Person: | 21 | class Person: | ||
22 | """Represent a Halloween in Pripyat person.""" | 22 | """Represent a Halloween in Pripyat person.""" | ||
23 | 23 | ||||
24 | def __init__(self, position): | 24 | def __init__(self, position): | ||
25 | """Initializator.""" | 25 | """Initializator.""" | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | def get_position(self): | 28 | def get_position(self): | ||
29 | """Get the current position of a person.""" | 29 | """Get the current position of a person.""" | ||
30 | return self._position | 30 | return self._position | ||
31 | 31 | ||||
32 | def set_position(self, position): | 32 | def set_position(self, position): | ||
33 | """Set the current position of a person.""" | 33 | """Set the current position of a person.""" | ||
34 | self._position = position | 34 | self._position = position | ||
35 | 35 | ||||
36 | 36 | ||||
37 | class Kid(Person): | 37 | class Kid(Person): | ||
38 | """Represent a Halloween in Pripyat kid.""" | 38 | """Represent a Halloween in Pripyat kid.""" | ||
39 | 39 | ||||
40 | CRITICAL_URANIUM = 20 | 40 | CRITICAL_URANIUM = 20 | ||
41 | 41 | ||||
42 | def __init__(self, position, initiative): | 42 | def __init__(self, position, initiative): | ||
43 | """Initializator.""" | 43 | """Initializator.""" | ||
44 | super().__init__(position) | 44 | super().__init__(position) | ||
45 | self._candies = set() | 45 | self._candies = set() | ||
46 | self._initiative = initiative | 46 | self._initiative = initiative | ||
47 | 47 | ||||
48 | def get_initiative(self): | 48 | def get_initiative(self): | ||
49 | """Get the initiative value of the kid.""" | 49 | """Get the initiative value of the kid.""" | ||
50 | return self._initiative | 50 | return self._initiative | ||
51 | 51 | ||||
52 | def add_candy(self, candy): | 52 | def add_candy(self, candy): | ||
53 | """Add a new candy to the set of owned candies.""" | 53 | """Add a new candy to the set of owned candies.""" | ||
54 | self._candies.add(candy) | 54 | self._candies.add(candy) | ||
55 | 55 | ||||
56 | def is_critical(self): | 56 | def is_critical(self): | ||
57 | """Check if the uranium mass in owned candies is critical.""" | 57 | """Check if the uranium mass in owned candies is critical.""" | ||
58 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 58 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
59 | return total > self.CRITICAL_URANIUM | 59 | return total > self.CRITICAL_URANIUM | ||
60 | 60 | ||||
61 | 61 | ||||
62 | class Host(Person): | 62 | class Host(Person): | ||
63 | """Represent a Halloween in Pripyat host.""" | 63 | """Represent a Halloween in Pripyat host.""" | ||
64 | 64 | ||||
65 | def __init__(self, position, candies): | 65 | def __init__(self, position, candies): | ||
66 | """Initializator.""" | 66 | """Initializator.""" | ||
67 | super().__init__(position) | 67 | super().__init__(position) | ||
68 | self._candies = {Candy(*candy) for candy in candies} | 68 | self._candies = {Candy(*candy) for candy in candies} | ||
69 | 69 | ||||
70 | def remove_candy(self, fun): | 70 | def remove_candy(self, fun): | ||
71 | """Remove a candy from the set of owned candies.""" | 71 | """Remove a candy from the set of owned candies.""" | ||
72 | if not self._candies: | 72 | if not self._candies: | ||
73 | return None | 73 | return None | ||
74 | candy = fun(self._candies) | 74 | candy = fun(self._candies) | ||
75 | self._candies.remove(candy) | 75 | self._candies.remove(candy) | ||
76 | return candy | 76 | return candy | ||
77 | 77 | ||||
78 | 78 | ||||
79 | class Visit: | 79 | class Visit: | ||
80 | """A single visit between a kid and a host.""" | 80 | """A single visit between a kid and a host.""" | ||
81 | 81 | ||||
82 | def __init__(self, kid, host, turn_index): | 82 | def __init__(self, kid, host, turn_index): | ||
83 | """Initializator.""" | 83 | """Initializator.""" | ||
84 | self.kid = kid | 84 | self.kid = kid | ||
85 | self.host = host | 85 | self.host = host | ||
86 | self.turn_index = turn_index | 86 | self.turn_index = turn_index | ||
87 | 87 | ||||
88 | 88 | ||||
89 | class FluxCapacitor: | 89 | class FluxCapacitor: | ||
90 | """Represent a Halloween in Pripyat simulator.""" | 90 | """Represent a Halloween in Pripyat simulator.""" | ||
91 | 91 | ||||
92 | def __init__(self, participants): | 92 | def __init__(self, participants): | ||
93 | """Initializator.""" | 93 | """Initializator.""" | ||
94 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | 94 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | ||
95 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | 95 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | ||
96 | self._visits = self._init_visits() | 96 | self._visits = self._init_visits() | ||
97 | 97 | ||||
98 | @staticmethod | 98 | @staticmethod | ||
99 | def _distance(kid, host): | 99 | def _distance(kid, host): | ||
100 | """Calculate dinstance between a kid and a host.""" | 100 | """Calculate dinstance between a kid and a host.""" | ||
101 | return math.dist(kid.get_position(), host.get_position()) | 101 | return math.dist(kid.get_position(), host.get_position()) | ||
102 | 102 | ||||
103 | @staticmethod | 103 | @staticmethod | ||
104 | def _select_candy(candies): | 104 | def _select_candy(candies): | ||
105 | """Return a function to select the candy with highest mass from a set of candies.""" | 105 | """Return a function to select the candy with highest mass from a set of candies.""" | ||
106 | return max(candies, key=lambda candy: candy.get_mass()) | 106 | return max(candies, key=lambda candy: candy.get_mass()) | ||
107 | 107 | ||||
108 | @classmethod | 108 | @classmethod | ||
109 | def _get_closest_host_of_kid(cls, kid, hosts): | 109 | def _get_closest_host_of_kid(cls, kid, hosts): | ||
110 | """Get the host that is closest to a particular kid.""" | 110 | """Get the host that is closest to a particular kid.""" | ||
111 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | 111 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||
112 | 112 | ||||
113 | def _init_visits(self): | 113 | def _init_visits(self): | ||
114 | """Calculate all visits for the night.""" | 114 | """Calculate all visits for the night.""" | ||
115 | visits = [] | 115 | visits = [] | ||
116 | for kid in self._kids: | 116 | for kid in self._kids: | ||
117 | hosts = self._hosts[:] | 117 | hosts = self._hosts[:] | ||
118 | turn_index = 0 | 118 | turn_index = 0 | ||
119 | while hosts: | 119 | while hosts: | ||
120 | host = self._get_closest_host_of_kid(kid, hosts) | 120 | host = self._get_closest_host_of_kid(kid, hosts) | ||
121 | hosts.remove(host) | 121 | hosts.remove(host) | ||
122 | visits.append(Visit(kid, host, turn_index)) | 122 | visits.append(Visit(kid, host, turn_index)) | ||
123 | turn_index += 1 | 123 | turn_index += 1 | ||
124 | kid.set_position(host.get_position()) | 124 | kid.set_position(host.get_position()) | ||
125 | return visits | 125 | return visits | ||
126 | 126 | ||||
127 | def _pop_visits(self): | 127 | def _pop_visits(self): | ||
128 | """Remove next visits from all-visits set and return them.""" | 128 | """Remove next visits from all-visits set and return them.""" | ||
129 | popped_visits = [] | 129 | popped_visits = [] | ||
130 | next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index | 130 | next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index | ||
131 | for visit in self._visits: | 131 | for visit in self._visits: | ||
132 | if visit.turn_index == next_turn: | 132 | if visit.turn_index == next_turn: | ||
133 | popped_visits.append(visit) | 133 | popped_visits.append(visit) | ||
134 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | 134 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | ||
135 | return popped_visits | 135 | return popped_visits | ||
136 | 136 | ||||
137 | def _make_a_visit(self): | 137 | def _make_a_visit(self): | ||
138 | """Make a single visit for the night and return exchanges for them.""" | 138 | """Make a single visit for the night and return exchanges for them.""" | ||
139 | if not self._visits: | 139 | if not self._visits: | ||
140 | return None | 140 | return None | ||
141 | visits = self._pop_visits() | 141 | visits = self._pop_visits() | ||
142 | exchanges = {} | 142 | exchanges = {} | ||
143 | for visit in visits: | 143 | for visit in visits: | ||
144 | exchanges.setdefault(visit.host, []).append(visit.kid) | 144 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
145 | return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()} | 145 | return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()} | ||
146 | 146 | ||||
147 | def _exchange_candy(self, exchanges): | 147 | def _exchange_candy(self, exchanges): | ||
148 | """Make a candy exchange.""" | 148 | """Make a candy exchange.""" | ||
149 | deads = set() | 149 | deads = set() | ||
150 | for host, kids in exchanges.items(): | 150 | for host, kids in exchanges.items(): | ||
151 | for kid in kids: | 151 | for kid in kids: | ||
152 | candy = host.remove_candy(self._select_candy) | 152 | candy = host.remove_candy(self._select_candy) | ||
153 | if candy: | 153 | if candy: | ||
154 | kid.add_candy(candy) | 154 | kid.add_candy(candy) | ||
155 | if kid.is_critical(): | 155 | if kid.is_critical(): | ||
156 | deads.add(kid) | 156 | deads.add(kid) | ||
157 | return deads or None | 157 | return deads or None | ||
158 | 158 | ||||
159 | def get_victim(self): | 159 | def get_victim(self): | ||
160 | """Get the first victim of the night.""" | 160 | """Get the first victim of the night.""" | ||
161 | while True: | 161 | while True: | ||
162 | if not (exchanges:=self._make_a_visit()): | 162 | if not (exchanges:=self._make_a_visit()): | ||
163 | return None | 163 | return None | ||
164 | if victim:=self._exchange_candy(exchanges): | 164 | if victim:=self._exchange_candy(exchanges): | ||
165 | return victim | 165 | return victim |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
4 | class Candy: | 4 | class Candy: | ||
n | n | 5 | """Represent a Halloween in Pripyat candy.""" | ||
5 | 6 | ||||
6 | def __init__(self, mass, uranium): | 7 | def __init__(self, mass, uranium): | ||
n | n | 8 | """Initializator.""" | ||
7 | self._mass = mass | 9 | self._mass = mass | ||
8 | self._uranium = uranium | 10 | self._uranium = uranium | ||
9 | 11 | ||||
10 | def get_uranium_quantity(self): | 12 | def get_uranium_quantity(self): | ||
n | n | 13 | """Get absolute content of uranium.""" | ||
11 | return self._mass * self._uranium | 14 | return self._mass * self._uranium | ||
12 | 15 | ||||
13 | def get_mass(self): | 16 | def get_mass(self): | ||
n | n | 17 | """Get the mass of the candy.""" | ||
14 | return self._mass | 18 | return self._mass | ||
15 | 19 | ||||
16 | 20 | ||||
17 | class Person: | 21 | class Person: | ||
n | n | 22 | """Represent a Halloween in Pripyat person.""" | ||
18 | 23 | ||||
19 | def __init__(self, position): | 24 | def __init__(self, position): | ||
n | n | 25 | """Initializator.""" | ||
20 | self._position = position | 26 | self._position = position | ||
21 | 27 | ||||
22 | def get_position(self): | 28 | def get_position(self): | ||
n | n | 29 | """Get the current position of a person.""" | ||
23 | return self._position | 30 | return self._position | ||
24 | 31 | ||||
25 | def set_position(self, position): | 32 | def set_position(self, position): | ||
n | n | 33 | """Set the current position of a person.""" | ||
26 | self._position = position | 34 | self._position = position | ||
27 | 35 | ||||
28 | 36 | ||||
29 | class Kid(Person): | 37 | class Kid(Person): | ||
n | n | 38 | """Represent a Halloween in Pripyat kid.""" | ||
30 | 39 | ||||
31 | CRITICAL_URANIUM = 20 | 40 | CRITICAL_URANIUM = 20 | ||
32 | 41 | ||||
n | 33 | def __init__(self, position, speed): | n | 42 | def __init__(self, position, initiative): |
43 | """Initializator.""" | ||||
34 | super().__init__(position) | 44 | super().__init__(position) | ||
35 | self._candies = set() | 45 | self._candies = set() | ||
n | 36 | self._speed = speed | n | 46 | self._initiative = initiative |
37 | 47 | ||||
n | 38 | def get_speed(self): | n | 48 | def get_initiative(self): |
49 | """Get the initiative value of the kid.""" | ||||
39 | return self._speed | 50 | return self._initiative | ||
40 | 51 | ||||
41 | def add_candy(self, candy): | 52 | def add_candy(self, candy): | ||
n | n | 53 | """Add a new candy to the set of owned candies.""" | ||
42 | self._candies.add(candy) | 54 | self._candies.add(candy) | ||
43 | 55 | ||||
44 | def is_critical(self): | 56 | def is_critical(self): | ||
n | n | 57 | """Check if the uranium mass in owned candies is critical.""" | ||
45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 58 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
46 | return total > self.CRITICAL_URANIUM | 59 | return total > self.CRITICAL_URANIUM | ||
47 | 60 | ||||
48 | 61 | ||||
49 | class Host(Person): | 62 | class Host(Person): | ||
n | n | 63 | """Represent a Halloween in Pripyat host.""" | ||
50 | 64 | ||||
51 | def __init__(self, position, candies): | 65 | def __init__(self, position, candies): | ||
n | n | 66 | """Initializator.""" | ||
52 | super().__init__(position) | 67 | super().__init__(position) | ||
53 | self._candies = {Candy(*candy) for candy in candies} | 68 | self._candies = {Candy(*candy) for candy in candies} | ||
54 | 69 | ||||
55 | def remove_candy(self, fun): | 70 | def remove_candy(self, fun): | ||
n | n | 71 | """Remove a candy from the set of owned candies.""" | ||
56 | if not self._candies: | 72 | if not self._candies: | ||
57 | return None | 73 | return None | ||
58 | candy = fun(self._candies) | 74 | candy = fun(self._candies) | ||
59 | self._candies.remove(candy) | 75 | self._candies.remove(candy) | ||
60 | return candy | 76 | return candy | ||
61 | 77 | ||||
62 | 78 | ||||
63 | class Visit: | 79 | class Visit: | ||
n | n | 80 | """A single visit between a kid and a host.""" | ||
64 | 81 | ||||
n | 65 | def __init__(self, kid, host, absolute_time): | n | 82 | def __init__(self, kid, host, turn_index): |
83 | """Initializator.""" | ||||
66 | self.kid = kid | 84 | self.kid = kid | ||
67 | self.host = host | 85 | self.host = host | ||
n | 68 | self.absolute_time = absolute_time | n | 86 | self.turn_index = turn_index |
69 | 87 | ||||
70 | 88 | ||||
71 | class FluxCapacitor: | 89 | class FluxCapacitor: | ||
n | n | 90 | """Represent a Halloween in Pripyat simulator.""" | ||
72 | 91 | ||||
73 | def __init__(self, participants): | 92 | def __init__(self, participants): | ||
n | n | 93 | """Initializator.""" | ||
74 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | 94 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | ||
75 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | 95 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | ||
76 | self._visits = self._init_visits() | 96 | self._visits = self._init_visits() | ||
77 | 97 | ||||
78 | @staticmethod | 98 | @staticmethod | ||
79 | def _distance(kid, host): | 99 | def _distance(kid, host): | ||
n | n | 100 | """Calculate dinstance between a kid and a host.""" | ||
80 | return math.dist(kid.get_position(), host.get_position()) | 101 | return math.dist(kid.get_position(), host.get_position()) | ||
81 | 102 | ||||
82 | @staticmethod | 103 | @staticmethod | ||
83 | def _select_candy(candies): | 104 | def _select_candy(candies): | ||
n | n | 105 | """Return a function to select the candy with highest mass from a set of candies.""" | ||
84 | return max(candies, key=lambda candy: candy.get_mass()) | 106 | return max(candies, key=lambda candy: candy.get_mass()) | ||
85 | 107 | ||||
86 | @classmethod | 108 | @classmethod | ||
87 | def _get_closest_host_of_kid(cls, kid, hosts): | 109 | def _get_closest_host_of_kid(cls, kid, hosts): | ||
n | n | 110 | """Get the host that is closest to a particular kid.""" | ||
88 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | 111 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||
89 | 112 | ||||
90 | def _init_visits(self): | 113 | def _init_visits(self): | ||
n | n | 114 | """Calculate all visits for the night.""" | ||
91 | visits = [] | 115 | visits = [] | ||
92 | for kid in self._kids: | 116 | for kid in self._kids: | ||
n | 93 | absolute_time = 0 | n | ||
94 | hosts = self._hosts[:] | 117 | hosts = self._hosts[:] | ||
n | n | 118 | turn_index = 0 | ||
95 | while hosts: | 119 | while hosts: | ||
96 | host = self._get_closest_host_of_kid(kid, hosts) | 120 | host = self._get_closest_host_of_kid(kid, hosts) | ||
97 | hosts.remove(host) | 121 | hosts.remove(host) | ||
n | 98 | distance = math.dist(kid.get_position(), host.get_position()) | n | ||
99 | time_delta = distance / kid.get_speed() | ||||
100 | absolute_time = absolute_time + time_delta | ||||
101 | visits.append(Visit(kid, host, absolute_time)) | 122 | visits.append(Visit(kid, host, turn_index)) | ||
123 | turn_index += 1 | ||||
124 | kid.set_position(host.get_position()) | ||||
102 | return visits | 125 | return visits | ||
n | 103 | n | 126 | ||
104 | def _pop_visits(self): | 127 | def _pop_visits(self): | ||
n | n | 128 | """Remove next visits from all-visits set and return them.""" | ||
105 | popped_visits = [] | 129 | popped_visits = [] | ||
n | 106 | next_time = min(self._visits, key=lambda visit: visit.absolute_time).absolute_time | n | 130 | next_turn = min(self._visits, key=lambda visit: visit.turn_index).turn_index |
107 | for visit in self._visits: | 131 | for visit in self._visits: | ||
n | 108 | if visit.absolute_time == next_time: | n | 132 | if visit.turn_index == next_turn: |
109 | popped_visits.append(visit) | 133 | popped_visits.append(visit) | ||
110 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | 134 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | ||
111 | return popped_visits | 135 | return popped_visits | ||
112 | 136 | ||||
113 | def _make_a_visit(self): | 137 | def _make_a_visit(self): | ||
n | n | 138 | """Make a single visit for the night and return exchanges for them.""" | ||
114 | if not self._visits: | 139 | if not self._visits: | ||
115 | return None | 140 | return None | ||
116 | visits = self._pop_visits() | 141 | visits = self._pop_visits() | ||
117 | exchanges = {} | 142 | exchanges = {} | ||
118 | for visit in visits: | 143 | for visit in visits: | ||
119 | exchanges.setdefault(visit.host, []).append(visit.kid) | 144 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
n | 120 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges.items()} | n | 145 | return {host: sorted(kids, key=lambda kid: kid.get_initiative(), reverse=True) for host, kids in exchanges.items()} |
121 | 146 | ||||
122 | def _exchange_candy(self, exchanges): | 147 | def _exchange_candy(self, exchanges): | ||
n | n | 148 | """Make a candy exchange.""" | ||
149 | deads = set() | ||||
123 | for host, kids in exchanges.items(): | 150 | for host, kids in exchanges.items(): | ||
124 | for kid in kids: | 151 | for kid in kids: | ||
125 | candy = host.remove_candy(self._select_candy) | 152 | candy = host.remove_candy(self._select_candy) | ||
126 | if candy: | 153 | if candy: | ||
127 | kid.add_candy(candy) | 154 | kid.add_candy(candy) | ||
128 | if kid.is_critical(): | 155 | if kid.is_critical(): | ||
n | 129 | return kid | n | 156 | deads.add(kid) |
130 | return None | 157 | return deads or None | ||
131 | 158 | ||||
132 | def get_victim(self): | 159 | def get_victim(self): | ||
t | t | 160 | """Get the first victim of the night.""" | ||
133 | while True: | 161 | while True: | ||
134 | if not (exchanges:=self._make_a_visit()): | 162 | if not (exchanges:=self._make_a_visit()): | ||
135 | return None | 163 | return None | ||
136 | if victim:=self._exchange_candy(exchanges): | 164 | if victim:=self._exchange_candy(exchanges): | ||
137 | return victim | 165 | return victim |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
4 | class Candy: | 4 | class Candy: | ||
5 | 5 | ||||
6 | def __init__(self, mass, uranium): | 6 | def __init__(self, mass, uranium): | ||
7 | self._mass = mass | 7 | self._mass = mass | ||
8 | self._uranium = uranium | 8 | self._uranium = uranium | ||
9 | 9 | ||||
10 | def get_uranium_quantity(self): | 10 | def get_uranium_quantity(self): | ||
11 | return self._mass * self._uranium | 11 | return self._mass * self._uranium | ||
12 | 12 | ||||
13 | def get_mass(self): | 13 | def get_mass(self): | ||
14 | return self._mass | 14 | return self._mass | ||
15 | 15 | ||||
16 | 16 | ||||
17 | class Person: | 17 | class Person: | ||
18 | 18 | ||||
19 | def __init__(self, position): | 19 | def __init__(self, position): | ||
20 | self._position = position | 20 | self._position = position | ||
21 | 21 | ||||
22 | def get_position(self): | 22 | def get_position(self): | ||
23 | return self._position | 23 | return self._position | ||
24 | 24 | ||||
25 | def set_position(self, position): | 25 | def set_position(self, position): | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | 28 | ||||
29 | class Kid(Person): | 29 | class Kid(Person): | ||
30 | 30 | ||||
31 | CRITICAL_URANIUM = 20 | 31 | CRITICAL_URANIUM = 20 | ||
32 | 32 | ||||
33 | def __init__(self, position, speed): | 33 | def __init__(self, position, speed): | ||
34 | super().__init__(position) | 34 | super().__init__(position) | ||
35 | self._candies = set() | 35 | self._candies = set() | ||
36 | self._speed = speed | 36 | self._speed = speed | ||
37 | 37 | ||||
38 | def get_speed(self): | 38 | def get_speed(self): | ||
39 | return self._speed | 39 | return self._speed | ||
40 | 40 | ||||
41 | def add_candy(self, candy): | 41 | def add_candy(self, candy): | ||
42 | self._candies.add(candy) | 42 | self._candies.add(candy) | ||
43 | 43 | ||||
44 | def is_critical(self): | 44 | def is_critical(self): | ||
45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
46 | return total > self.CRITICAL_URANIUM | 46 | return total > self.CRITICAL_URANIUM | ||
47 | 47 | ||||
48 | 48 | ||||
49 | class Host(Person): | 49 | class Host(Person): | ||
50 | 50 | ||||
51 | def __init__(self, position, candies): | 51 | def __init__(self, position, candies): | ||
52 | super().__init__(position) | 52 | super().__init__(position) | ||
53 | self._candies = {Candy(*candy) for candy in candies} | 53 | self._candies = {Candy(*candy) for candy in candies} | ||
54 | 54 | ||||
55 | def remove_candy(self, fun): | 55 | def remove_candy(self, fun): | ||
56 | if not self._candies: | 56 | if not self._candies: | ||
57 | return None | 57 | return None | ||
58 | candy = fun(self._candies) | 58 | candy = fun(self._candies) | ||
59 | self._candies.remove(candy) | 59 | self._candies.remove(candy) | ||
60 | return candy | 60 | return candy | ||
61 | 61 | ||||
62 | 62 | ||||
63 | class Visit: | 63 | class Visit: | ||
64 | 64 | ||||
65 | def __init__(self, kid, host, absolute_time): | 65 | def __init__(self, kid, host, absolute_time): | ||
66 | self.kid = kid | 66 | self.kid = kid | ||
67 | self.host = host | 67 | self.host = host | ||
68 | self.absolute_time = absolute_time | 68 | self.absolute_time = absolute_time | ||
69 | 69 | ||||
70 | 70 | ||||
71 | class FluxCapacitor: | 71 | class FluxCapacitor: | ||
72 | 72 | ||||
73 | def __init__(self, participants): | 73 | def __init__(self, participants): | ||
74 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | 74 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) | ||
75 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | 75 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | ||
76 | self._visits = self._init_visits() | 76 | self._visits = self._init_visits() | ||
77 | 77 | ||||
78 | @staticmethod | 78 | @staticmethod | ||
79 | def _distance(kid, host): | 79 | def _distance(kid, host): | ||
80 | return math.dist(kid.get_position(), host.get_position()) | 80 | return math.dist(kid.get_position(), host.get_position()) | ||
81 | 81 | ||||
82 | @staticmethod | 82 | @staticmethod | ||
83 | def _select_candy(candies): | 83 | def _select_candy(candies): | ||
84 | return max(candies, key=lambda candy: candy.get_mass()) | 84 | return max(candies, key=lambda candy: candy.get_mass()) | ||
85 | 85 | ||||
86 | @classmethod | 86 | @classmethod | ||
87 | def _get_closest_host_of_kid(cls, kid, hosts): | 87 | def _get_closest_host_of_kid(cls, kid, hosts): | ||
88 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | 88 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||
89 | 89 | ||||
90 | def _init_visits(self): | 90 | def _init_visits(self): | ||
91 | visits = [] | 91 | visits = [] | ||
92 | for kid in self._kids: | 92 | for kid in self._kids: | ||
93 | absolute_time = 0 | 93 | absolute_time = 0 | ||
94 | hosts = self._hosts[:] | 94 | hosts = self._hosts[:] | ||
95 | while hosts: | 95 | while hosts: | ||
96 | host = self._get_closest_host_of_kid(kid, hosts) | 96 | host = self._get_closest_host_of_kid(kid, hosts) | ||
97 | hosts.remove(host) | 97 | hosts.remove(host) | ||
98 | distance = math.dist(kid.get_position(), host.get_position()) | 98 | distance = math.dist(kid.get_position(), host.get_position()) | ||
99 | time_delta = distance / kid.get_speed() | 99 | time_delta = distance / kid.get_speed() | ||
100 | absolute_time = absolute_time + time_delta | 100 | absolute_time = absolute_time + time_delta | ||
101 | visits.append(Visit(kid, host, absolute_time)) | 101 | visits.append(Visit(kid, host, absolute_time)) | ||
102 | return visits | 102 | return visits | ||
103 | 103 | ||||
104 | def _pop_visits(self): | 104 | def _pop_visits(self): | ||
105 | popped_visits = [] | 105 | popped_visits = [] | ||
106 | next_time = min(self._visits, key=lambda visit: visit.absolute_time).absolute_time | 106 | next_time = min(self._visits, key=lambda visit: visit.absolute_time).absolute_time | ||
107 | for visit in self._visits: | 107 | for visit in self._visits: | ||
108 | if visit.absolute_time == next_time: | 108 | if visit.absolute_time == next_time: | ||
109 | popped_visits.append(visit) | 109 | popped_visits.append(visit) | ||
110 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | 110 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | ||
111 | return popped_visits | 111 | return popped_visits | ||
112 | 112 | ||||
113 | def _make_a_visit(self): | 113 | def _make_a_visit(self): | ||
114 | if not self._visits: | 114 | if not self._visits: | ||
115 | return None | 115 | return None | ||
116 | visits = self._pop_visits() | 116 | visits = self._pop_visits() | ||
117 | exchanges = {} | 117 | exchanges = {} | ||
118 | for visit in visits: | 118 | for visit in visits: | ||
119 | exchanges.setdefault(visit.host, []).append(visit.kid) | 119 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
120 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges.items()} | 120 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges.items()} | ||
121 | 121 | ||||
122 | def _exchange_candy(self, exchanges): | 122 | def _exchange_candy(self, exchanges): | ||
123 | for host, kids in exchanges.items(): | 123 | for host, kids in exchanges.items(): | ||
124 | for kid in kids: | 124 | for kid in kids: | ||
125 | candy = host.remove_candy(self._select_candy) | 125 | candy = host.remove_candy(self._select_candy) | ||
126 | if candy: | 126 | if candy: | ||
127 | kid.add_candy(candy) | 127 | kid.add_candy(candy) | ||
128 | if kid.is_critical(): | 128 | if kid.is_critical(): | ||
129 | return kid | 129 | return kid | ||
130 | return None | 130 | return None | ||
131 | 131 | ||||
132 | def get_victim(self): | 132 | def get_victim(self): | ||
133 | while True: | 133 | while True: | ||
134 | if not (exchanges:=self._make_a_visit()): | 134 | if not (exchanges:=self._make_a_visit()): | ||
135 | return None | 135 | return None | ||
136 | if victim:=self._exchange_candy(exchanges): | 136 | if victim:=self._exchange_candy(exchanges): | ||
137 | return victim | 137 | return victim | ||
t | 138 | t | |||
139 | Visit.__repr__ = lambda x: f"{x.kid} visits {x.host} at {x.absolute_time}" | ||||
140 | Person.__repr__ = lambda x: x.name | ||||
141 | kid1 = Kid((1, 1), 1) | ||||
142 | kid1.name = 'kid1' | ||||
143 | kid2 = Kid((7, 1), 2) | ||||
144 | kid2.name = 'kid2' | ||||
145 | host1 = Host((2, 1), [(15, 1), (14, 0)]) | ||||
146 | host1.name = 'host1' | ||||
147 | host2 = Host((6, 1), [(15, 1), (16, 1)]) | ||||
148 | host2.name = 'host2' | ||||
149 | print(FluxCapacitor({kid1, kid2, host1, host2}).get_victim()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
4 | class Candy: | 4 | class Candy: | ||
5 | 5 | ||||
6 | def __init__(self, mass, uranium): | 6 | def __init__(self, mass, uranium): | ||
7 | self._mass = mass | 7 | self._mass = mass | ||
8 | self._uranium = uranium | 8 | self._uranium = uranium | ||
9 | 9 | ||||
10 | def get_uranium_quantity(self): | 10 | def get_uranium_quantity(self): | ||
11 | return self._mass * self._uranium | 11 | return self._mass * self._uranium | ||
12 | 12 | ||||
13 | def get_mass(self): | 13 | def get_mass(self): | ||
14 | return self._mass | 14 | return self._mass | ||
15 | 15 | ||||
16 | 16 | ||||
17 | class Person: | 17 | class Person: | ||
18 | 18 | ||||
19 | def __init__(self, position): | 19 | def __init__(self, position): | ||
20 | self._position = position | 20 | self._position = position | ||
21 | 21 | ||||
22 | def get_position(self): | 22 | def get_position(self): | ||
23 | return self._position | 23 | return self._position | ||
24 | 24 | ||||
25 | def set_position(self, position): | 25 | def set_position(self, position): | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | 28 | ||||
29 | class Kid(Person): | 29 | class Kid(Person): | ||
30 | 30 | ||||
31 | CRITICAL_URANIUM = 20 | 31 | CRITICAL_URANIUM = 20 | ||
32 | 32 | ||||
33 | def __init__(self, position, speed): | 33 | def __init__(self, position, speed): | ||
34 | super().__init__(position) | 34 | super().__init__(position) | ||
n | 35 | self._candies = {} | n | 35 | self._candies = set() |
36 | self._speed = speed | 36 | self._speed = speed | ||
37 | 37 | ||||
38 | def get_speed(self): | 38 | def get_speed(self): | ||
39 | return self._speed | 39 | return self._speed | ||
40 | 40 | ||||
41 | def add_candy(self, candy): | 41 | def add_candy(self, candy): | ||
42 | self._candies.add(candy) | 42 | self._candies.add(candy) | ||
43 | 43 | ||||
44 | def is_critical(self): | 44 | def is_critical(self): | ||
45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
46 | return total > self.CRITICAL_URANIUM | 46 | return total > self.CRITICAL_URANIUM | ||
47 | 47 | ||||
48 | 48 | ||||
49 | class Host(Person): | 49 | class Host(Person): | ||
50 | 50 | ||||
51 | def __init__(self, position, candies): | 51 | def __init__(self, position, candies): | ||
52 | super().__init__(position) | 52 | super().__init__(position) | ||
n | 53 | self._candies = [Candy(*candy) for candy in candies] | n | 53 | self._candies = {Candy(*candy) for candy in candies} |
54 | 54 | ||||
55 | def remove_candy(self, fun): | 55 | def remove_candy(self, fun): | ||
56 | if not self._candies: | 56 | if not self._candies: | ||
57 | return None | 57 | return None | ||
n | n | 58 | candy = fun(self._candies) | ||
58 | return self._candies.remove(fun(self._candies)) | 59 | self._candies.remove(candy) | ||
60 | return candy | ||||
59 | 61 | ||||
60 | 62 | ||||
61 | class Visit: | 63 | class Visit: | ||
62 | 64 | ||||
63 | def __init__(self, kid, host, absolute_time): | 65 | def __init__(self, kid, host, absolute_time): | ||
64 | self.kid = kid | 66 | self.kid = kid | ||
65 | self.host = host | 67 | self.host = host | ||
66 | self.absolute_time = absolute_time | 68 | self.absolute_time = absolute_time | ||
67 | 69 | ||||
68 | 70 | ||||
69 | class FluxCapacitor: | 71 | class FluxCapacitor: | ||
70 | 72 | ||||
71 | def __init__(self, participants): | 73 | def __init__(self, participants): | ||
n | 72 | self._kids = filter(lambda x: type(x) is Kid, participants) | n | 74 | self._kids = list(filter(lambda x: type(x) is Kid, participants)) |
73 | self._hosts = filter(lambda x: type(x) is Host, participants) | 75 | self._hosts = list(filter(lambda x: type(x) is Host, participants)) | ||
74 | self._visits = self._get_visits() | 76 | self._visits = self._init_visits() | ||
75 | 77 | ||||
76 | @staticmethod | 78 | @staticmethod | ||
77 | def _distance(kid, host): | 79 | def _distance(kid, host): | ||
78 | return math.dist(kid.get_position(), host.get_position()) | 80 | return math.dist(kid.get_position(), host.get_position()) | ||
79 | 81 | ||||
80 | @staticmethod | 82 | @staticmethod | ||
81 | def _select_candy(candies): | 83 | def _select_candy(candies): | ||
82 | return max(candies, key=lambda candy: candy.get_mass()) | 84 | return max(candies, key=lambda candy: candy.get_mass()) | ||
83 | 85 | ||||
84 | @classmethod | 86 | @classmethod | ||
85 | def _get_closest_host_of_kid(cls, kid, hosts): | 87 | def _get_closest_host_of_kid(cls, kid, hosts): | ||
86 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | 88 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||
87 | 89 | ||||
n | 88 | def _get_visits(self): | n | 90 | def _init_visits(self): |
89 | visits = [] | 91 | visits = [] | ||
90 | for kid in self._kids: | 92 | for kid in self._kids: | ||
91 | absolute_time = 0 | 93 | absolute_time = 0 | ||
92 | hosts = self._hosts[:] | 94 | hosts = self._hosts[:] | ||
93 | while hosts: | 95 | while hosts: | ||
94 | host = self._get_closest_host_of_kid(kid, hosts) | 96 | host = self._get_closest_host_of_kid(kid, hosts) | ||
95 | hosts.remove(host) | 97 | hosts.remove(host) | ||
96 | distance = math.dist(kid.get_position(), host.get_position()) | 98 | distance = math.dist(kid.get_position(), host.get_position()) | ||
97 | time_delta = distance / kid.get_speed() | 99 | time_delta = distance / kid.get_speed() | ||
98 | absolute_time = absolute_time + time_delta | 100 | absolute_time = absolute_time + time_delta | ||
n | 99 | visits.append(kid, host, absolute_time) | n | 101 | visits.append(Visit(kid, host, absolute_time)) |
100 | return visits | 102 | return visits | ||
101 | 103 | ||||
102 | def _pop_visits(self): | 104 | def _pop_visits(self): | ||
n | 103 | poped_visits = {} | n | 105 | popped_visits = [] |
104 | next_time = min(self._visits, key=lambda visit: visit.absolute_time) | 106 | next_time = min(self._visits, key=lambda visit: visit.absolute_time).absolute_time | ||
105 | for visit in self._visits: | 107 | for visit in self._visits: | ||
106 | if visit.absolute_time == next_time: | 108 | if visit.absolute_time == next_time: | ||
n | 107 | poped_visits.update(self._visits.pop(visit)) | n | 109 | popped_visits.append(visit) |
110 | self._visits = [visit for visit in self._visits if visit not in popped_visits] | ||||
108 | return poped_visits | 111 | return popped_visits | ||
109 | 112 | ||||
110 | def _make_a_visit(self): | 113 | def _make_a_visit(self): | ||
n | n | 114 | if not self._visits: | ||
115 | return None | ||||
111 | visits = self._pop_visits() | 116 | visits = self._pop_visits() | ||
n | 112 | if not visits: | n | ||
113 | return None | ||||
114 | exchanges = {} | 117 | exchanges = {} | ||
115 | for visit in visits: | 118 | for visit in visits: | ||
116 | exchanges.setdefault(visit.host, []).append(visit.kid) | 119 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
n | 117 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges} | n | 120 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges.items()} |
118 | 121 | ||||
119 | def _exchange_candy(self, exchanges): | 122 | def _exchange_candy(self, exchanges): | ||
120 | for host, kids in exchanges.items(): | 123 | for host, kids in exchanges.items(): | ||
121 | for kid in kids: | 124 | for kid in kids: | ||
122 | candy = host.remove_candy(self._select_candy) | 125 | candy = host.remove_candy(self._select_candy) | ||
123 | if candy: | 126 | if candy: | ||
124 | kid.add_candy(candy) | 127 | kid.add_candy(candy) | ||
125 | if kid.is_critical(): | 128 | if kid.is_critical(): | ||
126 | return kid | 129 | return kid | ||
127 | return None | 130 | return None | ||
128 | 131 | ||||
129 | def get_victim(self): | 132 | def get_victim(self): | ||
130 | while True: | 133 | while True: | ||
131 | if not (exchanges:=self._make_a_visit()): | 134 | if not (exchanges:=self._make_a_visit()): | ||
132 | return None | 135 | return None | ||
133 | if victim:=self._exchange_candy(exchanges): | 136 | if victim:=self._exchange_candy(exchanges): | ||
134 | return victim | 137 | return victim | ||
t | t | 138 | |||
139 | Visit.__repr__ = lambda x: f"{x.kid} visits {x.host} at {x.absolute_time}" | ||||
140 | Person.__repr__ = lambda x: x.name | ||||
141 | kid1 = Kid((1, 1), 1) | ||||
142 | kid1.name = 'kid1' | ||||
143 | kid2 = Kid((7, 1), 2) | ||||
144 | kid2.name = 'kid2' | ||||
145 | host1 = Host((2, 1), [(15, 1), (14, 0)]) | ||||
146 | host1.name = 'host1' | ||||
147 | host2 = Host((6, 1), [(15, 1), (16, 1)]) | ||||
148 | host2.name = 'host2' | ||||
149 | print(FluxCapacitor({kid1, kid2, host1, host2}).get_victim()) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
4 | class Candy: | 4 | class Candy: | ||
5 | 5 | ||||
6 | def __init__(self, mass, uranium): | 6 | def __init__(self, mass, uranium): | ||
7 | self._mass = mass | 7 | self._mass = mass | ||
8 | self._uranium = uranium | 8 | self._uranium = uranium | ||
9 | 9 | ||||
10 | def get_uranium_quantity(self): | 10 | def get_uranium_quantity(self): | ||
11 | return self._mass * self._uranium | 11 | return self._mass * self._uranium | ||
12 | 12 | ||||
13 | def get_mass(self): | 13 | def get_mass(self): | ||
14 | return self._mass | 14 | return self._mass | ||
15 | 15 | ||||
16 | 16 | ||||
17 | class Person: | 17 | class Person: | ||
18 | 18 | ||||
19 | def __init__(self, position): | 19 | def __init__(self, position): | ||
20 | self._position = position | 20 | self._position = position | ||
21 | 21 | ||||
22 | def get_position(self): | 22 | def get_position(self): | ||
23 | return self._position | 23 | return self._position | ||
24 | 24 | ||||
25 | def set_position(self, position): | 25 | def set_position(self, position): | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | 28 | ||||
29 | class Kid(Person): | 29 | class Kid(Person): | ||
30 | 30 | ||||
31 | CRITICAL_URANIUM = 20 | 31 | CRITICAL_URANIUM = 20 | ||
32 | 32 | ||||
33 | def __init__(self, position, speed): | 33 | def __init__(self, position, speed): | ||
34 | super().__init__(position) | 34 | super().__init__(position) | ||
35 | self._candies = {} | 35 | self._candies = {} | ||
36 | self._speed = speed | 36 | self._speed = speed | ||
37 | 37 | ||||
38 | def get_speed(self): | 38 | def get_speed(self): | ||
39 | return self._speed | 39 | return self._speed | ||
40 | 40 | ||||
41 | def add_candy(self, candy): | 41 | def add_candy(self, candy): | ||
42 | self._candies.add(candy) | 42 | self._candies.add(candy) | ||
43 | 43 | ||||
44 | def is_critical(self): | 44 | def is_critical(self): | ||
45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
46 | return total > self.CRITICAL_URANIUM | 46 | return total > self.CRITICAL_URANIUM | ||
47 | 47 | ||||
48 | 48 | ||||
49 | class Host(Person): | 49 | class Host(Person): | ||
50 | 50 | ||||
51 | def __init__(self, position, candies): | 51 | def __init__(self, position, candies): | ||
52 | super().__init__(position) | 52 | super().__init__(position) | ||
53 | self._candies = [Candy(*candy) for candy in candies] | 53 | self._candies = [Candy(*candy) for candy in candies] | ||
54 | 54 | ||||
55 | def remove_candy(self, fun): | 55 | def remove_candy(self, fun): | ||
56 | if not self._candies: | 56 | if not self._candies: | ||
57 | return None | 57 | return None | ||
58 | return self._candies.remove(fun(self._candies)) | 58 | return self._candies.remove(fun(self._candies)) | ||
59 | 59 | ||||
60 | 60 | ||||
n | n | 61 | class Visit: | ||
62 | |||||
63 | def __init__(self, kid, host, absolute_time): | ||||
64 | self.kid = kid | ||||
65 | self.host = host | ||||
66 | self.absolute_time = absolute_time | ||||
67 | |||||
68 | |||||
61 | class FluxCapacitor: | 69 | class FluxCapacitor: | ||
62 | 70 | ||||
63 | def __init__(self, participants): | 71 | def __init__(self, participants): | ||
64 | self._kids = filter(lambda x: type(x) is Kid, participants) | 72 | self._kids = filter(lambda x: type(x) is Kid, participants) | ||
65 | self._hosts = filter(lambda x: type(x) is Host, participants) | 73 | self._hosts = filter(lambda x: type(x) is Host, participants) | ||
n | 66 | self._visits = {kid: [] for kid in self._kids} | n | 74 | self._visits = self._get_visits() |
67 | self._next_time_of_arrival = {kid: self._get_next_host_and_time_of_kid(kid) for kid in self._kids} | 75 | |||
76 | @staticmethod | ||||
77 | def _distance(kid, host): | ||||
78 | return math.dist(kid.get_position(), host.get_position()) | ||||
68 | 79 | ||||
69 | @staticmethod | 80 | @staticmethod | ||
70 | def _select_candy(candies): | 81 | def _select_candy(candies): | ||
71 | return max(candies, key=lambda candy: candy.get_mass()) | 82 | return max(candies, key=lambda candy: candy.get_mass()) | ||
n | n | 83 | |||
84 | @classmethod | ||||
85 | def _get_closest_host_of_kid(cls, kid, hosts): | ||||
86 | return min(hosts, key=lambda host: (cls._distance(kid, host), host.get_position())) | ||||
87 | |||||
88 | def _get_visits(self): | ||||
89 | visits = [] | ||||
90 | for kid in self._kids: | ||||
91 | absolute_time = 0 | ||||
92 | hosts = self._hosts[:] | ||||
93 | while hosts: | ||||
94 | host = self._get_closest_host_of_kid(kid, hosts) | ||||
95 | hosts.remove(host) | ||||
96 | distance = math.dist(kid.get_position(), host.get_position()) | ||||
97 | time_delta = distance / kid.get_speed() | ||||
98 | absolute_time = absolute_time + time_delta | ||||
99 | visits.append(kid, host, absolute_time) | ||||
100 | return visits | ||||
101 | |||||
102 | def _pop_visits(self): | ||||
103 | poped_visits = {} | ||||
104 | next_time = min(self._visits, key=lambda visit: visit.absolute_time) | ||||
105 | for visit in self._visits: | ||||
106 | if visit.absolute_time == next_time: | ||||
107 | poped_visits.update(self._visits.pop(visit)) | ||||
108 | return poped_visits | ||||
72 | 109 | ||||
n | 73 | def _get_next_host_and_time_of_kid(self, kid): | n | 110 | def _make_a_visit(self): |
74 | if not(host:=self._get_closest_host_to_kid(kid)): | 111 | visits = self._pop_visits() | ||
75 | return None, None | 112 | if not visits: | ||
76 | distance = math.dist(kid.get_position(), host.get_position()) | ||||
77 | return host, distance / kid.get_speed() | ||||
78 | |||||
79 | def _get_closest_host_of_kid(self, kid): | ||||
80 | distances = {} | ||||
81 | for host in self._hosts: | ||||
82 | if host in self._visits[kid]: | ||||
83 | continue | ||||
84 | distances[host] = math.dist(kid.get_position(), host.get_position()) | ||||
85 | if distances: | ||||
86 | min_distance = min(distances) | ||||
87 | hosts = [host for host, distance in distances.items() if distance == min_distance] | ||||
88 | return min(hosts, key=lambda host: host.get_position()) | ||||
89 | return None | 113 | return None | ||
90 | 114 | exchanges = {} | |||
91 | def _move_a_turn(self): | 115 | for visit in visits: | ||
92 | next_arrival_time = min([arrival_time for _, arrival_time in self._next_time_of_arrival.values() if arrival_time is not None]) | ||||
93 | next_arrivals = {kid: arrival for kid, arrival in self._next_time_of_arrival.items() if arrival[1] == next_arrival_time} | ||||
94 | all_arrivals = {} | ||||
95 | for kid, (host, _) in next_arrivals.items(): | ||||
96 | all_arrivals.setdefault(host, []).append(kid) | 116 | exchanges.setdefault(visit.host, []).append(visit.kid) | ||
97 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in all_arrivals} | 117 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in exchanges} | ||
98 | 118 | ||||
99 | def _exchange_candy(self, exchanges): | 119 | def _exchange_candy(self, exchanges): | ||
100 | for host, kids in exchanges.items(): | 120 | for host, kids in exchanges.items(): | ||
101 | for kid in kids: | 121 | for kid in kids: | ||
102 | candy = host.remove_candy(self._select_candy) | 122 | candy = host.remove_candy(self._select_candy) | ||
103 | if candy: | 123 | if candy: | ||
104 | kid.add_candy(candy) | 124 | kid.add_candy(candy) | ||
105 | if kid.is_critical(): | 125 | if kid.is_critical(): | ||
106 | return kid | 126 | return kid | ||
n | 107 | next_host, time_delta = self._get_next_host_and_time_of_kid(kid) | n | ||
108 | if next_host is None: | ||||
109 | self._next_time_of_arrival[kid] = None, None | ||||
110 | else: | ||||
111 | self._next_time_of_arrival[kid] = next_host, time_delta + self._next_time_of_arrival[kid][1] | ||||
112 | return None | 127 | return None | ||
113 | 128 | ||||
114 | def get_victim(self): | 129 | def get_victim(self): | ||
115 | while True: | 130 | while True: | ||
t | 116 | if not (exchanges:=self._move_a_turn()): | t | 131 | if not (exchanges:=self._make_a_visit()): |
117 | return None | 132 | return None | ||
118 | if victim:=self._exchange_candy(exchanges): | 133 | if victim:=self._exchange_candy(exchanges): | ||
119 | return victim | 134 | return victim |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | 3 | ||||
4 | class Candy: | 4 | class Candy: | ||
5 | 5 | ||||
6 | def __init__(self, mass, uranium): | 6 | def __init__(self, mass, uranium): | ||
7 | self._mass = mass | 7 | self._mass = mass | ||
8 | self._uranium = uranium | 8 | self._uranium = uranium | ||
9 | 9 | ||||
10 | def get_uranium_quantity(self): | 10 | def get_uranium_quantity(self): | ||
11 | return self._mass * self._uranium | 11 | return self._mass * self._uranium | ||
12 | 12 | ||||
13 | def get_mass(self): | 13 | def get_mass(self): | ||
14 | return self._mass | 14 | return self._mass | ||
15 | 15 | ||||
16 | 16 | ||||
17 | class Person: | 17 | class Person: | ||
18 | 18 | ||||
19 | def __init__(self, position): | 19 | def __init__(self, position): | ||
20 | self._position = position | 20 | self._position = position | ||
21 | 21 | ||||
22 | def get_position(self): | 22 | def get_position(self): | ||
23 | return self._position | 23 | return self._position | ||
24 | 24 | ||||
25 | def set_position(self, position): | 25 | def set_position(self, position): | ||
26 | self._position = position | 26 | self._position = position | ||
27 | 27 | ||||
28 | 28 | ||||
29 | class Kid(Person): | 29 | class Kid(Person): | ||
30 | 30 | ||||
31 | CRITICAL_URANIUM = 20 | 31 | CRITICAL_URANIUM = 20 | ||
32 | 32 | ||||
33 | def __init__(self, position, speed): | 33 | def __init__(self, position, speed): | ||
34 | super().__init__(position) | 34 | super().__init__(position) | ||
35 | self._candies = {} | 35 | self._candies = {} | ||
36 | self._speed = speed | 36 | self._speed = speed | ||
37 | 37 | ||||
38 | def get_speed(self): | 38 | def get_speed(self): | ||
39 | return self._speed | 39 | return self._speed | ||
40 | 40 | ||||
41 | def add_candy(self, candy): | 41 | def add_candy(self, candy): | ||
42 | self._candies.add(candy) | 42 | self._candies.add(candy) | ||
43 | 43 | ||||
44 | def is_critical(self): | 44 | def is_critical(self): | ||
45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | 45 | total = sum([candy.get_uranium_quantity() for candy in self._candies]) | ||
46 | return total > self.CRITICAL_URANIUM | 46 | return total > self.CRITICAL_URANIUM | ||
47 | 47 | ||||
48 | 48 | ||||
49 | class Host(Person): | 49 | class Host(Person): | ||
50 | 50 | ||||
51 | def __init__(self, position, candies): | 51 | def __init__(self, position, candies): | ||
52 | super().__init__(position) | 52 | super().__init__(position) | ||
53 | self._candies = [Candy(*candy) for candy in candies] | 53 | self._candies = [Candy(*candy) for candy in candies] | ||
54 | 54 | ||||
55 | def remove_candy(self, fun): | 55 | def remove_candy(self, fun): | ||
56 | if not self._candies: | 56 | if not self._candies: | ||
57 | return None | 57 | return None | ||
58 | return self._candies.remove(fun(self._candies)) | 58 | return self._candies.remove(fun(self._candies)) | ||
59 | 59 | ||||
60 | 60 | ||||
61 | class FluxCapacitor: | 61 | class FluxCapacitor: | ||
62 | 62 | ||||
63 | def __init__(self, participants): | 63 | def __init__(self, participants): | ||
64 | self._kids = filter(lambda x: type(x) is Kid, participants) | 64 | self._kids = filter(lambda x: type(x) is Kid, participants) | ||
65 | self._hosts = filter(lambda x: type(x) is Host, participants) | 65 | self._hosts = filter(lambda x: type(x) is Host, participants) | ||
66 | self._visits = {kid: [] for kid in self._kids} | 66 | self._visits = {kid: [] for kid in self._kids} | ||
67 | self._next_time_of_arrival = {kid: self._get_next_host_and_time_of_kid(kid) for kid in self._kids} | 67 | self._next_time_of_arrival = {kid: self._get_next_host_and_time_of_kid(kid) for kid in self._kids} | ||
68 | 68 | ||||
69 | @staticmethod | 69 | @staticmethod | ||
70 | def _select_candy(candies): | 70 | def _select_candy(candies): | ||
71 | return max(candies, key=lambda candy: candy.get_mass()) | 71 | return max(candies, key=lambda candy: candy.get_mass()) | ||
72 | 72 | ||||
73 | def _get_next_host_and_time_of_kid(self, kid): | 73 | def _get_next_host_and_time_of_kid(self, kid): | ||
74 | if not(host:=self._get_closest_host_to_kid(kid)): | 74 | if not(host:=self._get_closest_host_to_kid(kid)): | ||
75 | return None, None | 75 | return None, None | ||
76 | distance = math.dist(kid.get_position(), host.get_position()) | 76 | distance = math.dist(kid.get_position(), host.get_position()) | ||
77 | return host, distance / kid.get_speed() | 77 | return host, distance / kid.get_speed() | ||
78 | 78 | ||||
79 | def _get_closest_host_of_kid(self, kid): | 79 | def _get_closest_host_of_kid(self, kid): | ||
80 | distances = {} | 80 | distances = {} | ||
81 | for host in self._hosts: | 81 | for host in self._hosts: | ||
82 | if host in self._visits[kid]: | 82 | if host in self._visits[kid]: | ||
83 | continue | 83 | continue | ||
84 | distances[host] = math.dist(kid.get_position(), host.get_position()) | 84 | distances[host] = math.dist(kid.get_position(), host.get_position()) | ||
85 | if distances: | 85 | if distances: | ||
86 | min_distance = min(distances) | 86 | min_distance = min(distances) | ||
87 | hosts = [host for host, distance in distances.items() if distance == min_distance] | 87 | hosts = [host for host, distance in distances.items() if distance == min_distance] | ||
88 | return min(hosts, key=lambda host: host.get_position()) | 88 | return min(hosts, key=lambda host: host.get_position()) | ||
89 | return None | 89 | return None | ||
90 | 90 | ||||
91 | def _move_a_turn(self): | 91 | def _move_a_turn(self): | ||
92 | next_arrival_time = min([arrival_time for _, arrival_time in self._next_time_of_arrival.values() if arrival_time is not None]) | 92 | next_arrival_time = min([arrival_time for _, arrival_time in self._next_time_of_arrival.values() if arrival_time is not None]) | ||
93 | next_arrivals = {kid: arrival for kid, arrival in self._next_time_of_arrival.items() if arrival[1] == next_arrival_time} | 93 | next_arrivals = {kid: arrival for kid, arrival in self._next_time_of_arrival.items() if arrival[1] == next_arrival_time} | ||
94 | all_arrivals = {} | 94 | all_arrivals = {} | ||
95 | for kid, (host, _) in next_arrivals.items(): | 95 | for kid, (host, _) in next_arrivals.items(): | ||
96 | all_arrivals.setdefault(host, []).append(kid) | 96 | all_arrivals.setdefault(host, []).append(kid) | ||
97 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in all_arrivals} | 97 | return {host: sorted(kids, key=lambda kid: kid.get_speed()) for host, kids in all_arrivals} | ||
98 | 98 | ||||
99 | def _exchange_candy(self, exchanges): | 99 | def _exchange_candy(self, exchanges): | ||
100 | for host, kids in exchanges.items(): | 100 | for host, kids in exchanges.items(): | ||
101 | for kid in kids: | 101 | for kid in kids: | ||
102 | candy = host.remove_candy(self._select_candy) | 102 | candy = host.remove_candy(self._select_candy) | ||
103 | if candy: | 103 | if candy: | ||
104 | kid.add_candy(candy) | 104 | kid.add_candy(candy) | ||
105 | if kid.is_critical(): | 105 | if kid.is_critical(): | ||
106 | return kid | 106 | return kid | ||
t | t | 107 | next_host, time_delta = self._get_next_host_and_time_of_kid(kid) | ||
108 | if next_host is None: | ||||
109 | self._next_time_of_arrival[kid] = None, None | ||||
110 | else: | ||||
111 | self._next_time_of_arrival[kid] = next_host, time_delta + self._next_time_of_arrival[kid][1] | ||||
107 | return None | 112 | return None | ||
108 | 113 | ||||
109 | def get_victim(self): | 114 | def get_victim(self): | ||
110 | while True: | 115 | while True: | ||
111 | if not (exchanges:=self._move_a_turn()): | 116 | if not (exchanges:=self._move_a_turn()): | ||
112 | return None | 117 | return None | ||
113 | if victim:=self._exchange_candy(exchanges): | 118 | if victim:=self._exchange_candy(exchanges): | ||
114 | return victim | 119 | return victim |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|