From c32f1d0fe20d30cb8c3e55fb49c5cbcca4995c8c Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Mon, 21 Sep 2026 10:38:57 +0100 Subject: [PATCH] Deduplicate histories returned by H-selectors H-selectors are intended to be thought of as set-like. This ensures that if a history is selected more than once by an expression, it appears only once in the output. --- src/pygambit/gametree.pxi | 2 +- tests/test_hsel.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pygambit/gametree.pxi b/src/pygambit/gametree.pxi index cf1efd0ed8..f7b4ac3aff 100644 --- a/src/pygambit/gametree.pxi +++ b/src/pygambit/gametree.pxi @@ -227,7 +227,7 @@ class ExtensiveGame(Game): raise TypeError(f"_get_nodes(): unknown selector op {op!r}") if current is None: current = [self._root()] - return current + return list(dict.fromkeys(current)) def _get_histories(self, selector: Selector) -> list[History]: """Evaluate `selector` (an `H`-built expression) against this game, diff --git a/tests/test_hsel.py b/tests/test_hsel.py index e4a491c691..d1b1220b5b 100644 --- a/tests/test_hsel.py +++ b/tests/test_hsel.py @@ -199,3 +199,12 @@ def test_by_last_action_distinguishes_infosets_sharing_a_label(): frozenset({("R", "U")}), frozenset({("R", "D")}), } + + +def test_selector_deduplicates_plays(): + game = gbt.ExtensiveGame(players=["A", "B"]) + game.append_move(gbt.H.path(), "A", ["go", "stop"]) + game.append_move(gbt.H.path("go"), "B", ["go", "stay"]) + plays = game.get_histories(gbt.H.after("go").plays) + assert {h.actions for h in plays} == {("go", "go"), ("go", "stay")} + assert len(plays) == 2, f"expected 2 unique plays, got {len(plays)}: {plays}"