Skip to content

Commit 404bb98

Browse files
cmyuiclaude
andauthored
Use O(1) dict lookups for player collection get/contains (#747)
Replace linear O(n) scans with dict-based O(1) lookups for Players.get(), Players.__contains__(), append(), and remove(). Three private dicts (_by_token, _by_id, _by_name) are maintained alongside the list to enable constant-time lookups by token, id, or safe_name. Also fixes an existing bug in __contains__ where the parameter `player` shadowed the outer variable in the generator. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 02a8bc1 commit 404bb98

File tree

1 file changed

+18
-16
lines changed

1 file changed

+18
-16
lines changed

app/objects/collections.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,16 @@ class Players(list[Player]):
124124

125125
def __init__(self, *args: Any, **kwargs: Any) -> None:
126126
super().__init__(*args, **kwargs)
127+
self._by_token: dict[str, Player] = {}
128+
self._by_id: dict[int, Player] = {}
129+
self._by_name: dict[str, Player] = {}
127130

128131
def __iter__(self) -> Iterator[Player]:
129132
return super().__iter__()
130133

131134
def __contains__(self, player: object) -> bool:
132-
# allow us to either pass in the player
133-
# obj, or the player name as a string.
134135
if isinstance(player, str):
135-
return player in (player.name for player in self)
136+
return make_safe_name(player) in self._by_name
136137
else:
137138
return super().__contains__(player)
138139

@@ -172,17 +173,12 @@ def get(
172173
name: str | None = None,
173174
) -> Player | None:
174175
"""Get a player by token, id, or name from cache."""
175-
for player in self:
176-
if token is not None:
177-
if player.token == token:
178-
return player
179-
elif id is not None:
180-
if player.id == id:
181-
return player
182-
elif name is not None:
183-
if player.safe_name == make_safe_name(name):
184-
return player
185-
176+
if token is not None:
177+
return self._by_token.get(token)
178+
elif id is not None:
179+
return self._by_id.get(id)
180+
elif name is not None:
181+
return self._by_name.get(make_safe_name(name))
186182
return None
187183

188184
async def get_sql(
@@ -266,22 +262,28 @@ async def from_login(
266262
return None
267263

268264
def append(self, player: Player) -> None:
269-
"""Append `p` to the list."""
265+
"""Append `player` to the list."""
270266
if player in self:
271267
if app.settings.DEBUG:
272268
log(f"{player} double-added to global player list?")
273269
return
274270

275271
super().append(player)
272+
self._by_token[player.token] = player
273+
self._by_id[player.id] = player
274+
self._by_name[player.safe_name] = player
276275

277276
def remove(self, player: Player) -> None:
278-
"""Remove `p` from the list."""
277+
"""Remove `player` from the list."""
279278
if player not in self:
280279
if app.settings.DEBUG:
281280
log(f"{player} removed from player list when not online?")
282281
return
283282

284283
super().remove(player)
284+
del self._by_token[player.token]
285+
del self._by_id[player.id]
286+
del self._by_name[player.safe_name]
285287

286288

287289
async def initialize_ram_caches() -> None:

0 commit comments

Comments
 (0)