Skip to content

Commit 53e92b4

Browse files
committed
Document potentially surprising time complexities (closes #1000)
1 parent f76c387 commit 53e92b4

File tree

2 files changed

+49
-5
lines changed

2 files changed

+49
-5
lines changed

chess/pgn.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ def board(self) -> chess.Board:
215215
``Variant``) unless the ``FEN`` header tag is set.
216216
217217
It's a copy, so modifying the board will not alter the game.
218+
219+
Complexity is `O(n)`.
218220
"""
219221

220222
@abc.abstractmethod
@@ -226,11 +228,15 @@ def ply(self) -> int:
226228
227229
Usually this is equal to the number of parent nodes, but it may be
228230
more if the game was started from a custom position.
231+
232+
Complexity is `O(n)`.
229233
"""
230234

231235
def turn(self) -> Color:
232236
"""
233237
Gets the color to move at this node. See :data:`chess.Board.turn`.
238+
239+
Complexity is `O(n)`.
234240
"""
235241
return self.ply() % 2 == 0
236242

@@ -241,13 +247,21 @@ def root(self) -> GameNode:
241247
return node
242248

243249
def game(self) -> Game:
244-
"""Gets the root node, i.e., the game."""
250+
"""
251+
Gets the root node, i.e., the game.
252+
253+
Complexity is `O(n)`.
254+
"""
245255
root = self.root()
246256
assert isinstance(root, Game), "GameNode not rooted in Game"
247257
return root
248258

249259
def end(self) -> GameNode:
250-
"""Follows the main variation to the end and returns the last node."""
260+
"""
261+
Follows the main variation to the end and returns the last node.
262+
263+
Complexity is `O(n)`.
264+
"""
251265
node = self
252266

253267
while node.variations:
@@ -256,7 +270,11 @@ def end(self) -> GameNode:
256270
return node
257271

258272
def is_end(self) -> bool:
259-
"""Checks if this node is the last node in the current variation."""
273+
"""
274+
Checks if this node is the last node in the current variation.
275+
276+
Complexity is `O(1)`.
277+
"""
260278
return not self.variations
261279

262280
def starts_variation(self) -> bool:
@@ -267,14 +285,20 @@ def starts_variation(self) -> bool:
267285
268286
For example, in ``1. e4 e5 (1... c5 2. Nf3) 2. Nf3``, the node holding
269287
1... c5 starts a variation.
288+
289+
Complexity is `O(1)`.
270290
"""
271291
if not self.parent or not self.parent.variations:
272292
return False
273293

274294
return self.parent.variations[0] != self
275295

276296
def is_mainline(self) -> bool:
277-
"""Checks if the node is in the mainline of the game."""
297+
"""
298+
Checks if the node is in the mainline of the game.
299+
300+
Complexity is `O(n)`.
301+
"""
278302
node = self
279303

280304
while node.parent:
@@ -291,6 +315,8 @@ def is_main_variation(self) -> bool:
291315
"""
292316
Checks if this node is the first variation from the point of view of its
293317
parent. The root node is also in the main variation.
318+
319+
Complexity is `O(1)`.
294320
"""
295321
if not self.parent:
296322
return True
@@ -367,6 +393,8 @@ def next(self) -> Optional[ChildNode]:
367393
"""
368394
Returns the first node of the mainline after this node, or ``None`` if
369395
this node does not have any children.
396+
397+
Complexity is `O(1)`.
370398
"""
371399
return self.variations[0] if self.variations else None
372400

@@ -404,6 +432,8 @@ def eval(self) -> Optional[chess.engine.PovScore]:
404432
"""
405433
Parses the first valid ``[%eval ...]`` annotation in the comment of
406434
this node, if any.
435+
436+
Complexity is `O(n)`.
407437
"""
408438
match = EVAL_REGEX.search(self.comment)
409439
if not match:
@@ -428,6 +458,8 @@ def eval_depth(self) -> Optional[int]:
428458
"""
429459
Parses the first valid ``[%eval ...]`` annotation in the comment of
430460
this node and returns the corresponding depth, if any.
461+
462+
Complexity is `O(1)`.
431463
"""
432464
match = EVAL_REGEX.search(self.comment)
433465
return int(match.group("depth")) if match and match.group("depth") else None
@@ -670,6 +702,8 @@ def san(self) -> str:
670702
See :func:`chess.Board.san()`.
671703
672704
Do not call this on the root node.
705+
706+
Complexity is `O(n)`.
673707
"""
674708
return self.parent.board().san(self.move)
675709

@@ -679,11 +713,17 @@ def uci(self, *, chess960: Optional[bool] = None) -> str:
679713
See :func:`chess.Board.uci()`.
680714
681715
Do not call this on the root node.
716+
717+
Complexity is `O(n)`.
682718
"""
683719
return self.parent.board().uci(self.move, chess960=chess960)
684720

685721
def end(self) -> ChildNode:
686-
"""Follows the main variation to the end and returns the last node."""
722+
"""
723+
Follows the main variation to the end and returns the last node.
724+
725+
Complexity is `O(n)`.
726+
"""
687727
return typing.cast(ChildNode, super().end())
688728

689729
def _accept_node(self, parent_board: chess.Board, visitor: BaseVisitor[ResultT]) -> None:

docs/pgn.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ position of the game. The tree consists of one root node
4848
nodes (:class:`~chess.pgn.ChildNode`).
4949
Both extend :class:`~chess.pgn.GameNode`.
5050

51+
.. note:: Some basic methods have complexity `O(n)` for a game with n moves.
52+
When following a variation, it is often more efficient to use visitors
53+
or incrementally update state (like board, ply counter, or turn).
54+
5155
.. autoclass:: chess.pgn.GameNode
5256
:members:
5357

0 commit comments

Comments
 (0)