python-earley-shared-packed-forest-kbest
The solution is complete and verified. The full self-contained markdown is written to ~/SOLUTION.md, alongside the runnable earley_spf.py and test_earley_spf.py.
A textbook Earley implementation fails on this problem class in four ways:
origin == end, so the completed item and the waiter it must advance live in the same chart set. A waiter inserted after the completion is processed is silently dropped, and if you instead re-enqueue naively, an epsilon cycle (A→B, B→A, B→ε) spins forever. Fix: process positions left-to-right and saturate each with an agenda, indexing waiters[pos][X] and completed_at[end][origin], and let a processed waiter advance against an already-present nullable completion at completed_at[pos][pos]. Each item is inserted once (bounded by |rules|·(n+1)²), so it always terminates.(rule, dot, origin) is added once and the chart reaches a finite fixed point (direct and indirect).E→E+E | E*E | x, 16 operands ⇒ Catalan(15) = 9,694,845 trees). Fix: a true SPPF — one symbol node per span plus one intermediate node per Earley item, with hyperedges per split point. Counting is linear DP over the DAG; shared sub-forests are node identities.(tropical cost, derivation key). Ties break on the preorder sequence of rule indices / split positions, which is total and hash-order independent. Genuine cycles ⇒ InfiniteForestError (truly infinite count).cd ~
python3 -m py_compile earley_spf.py test_earley_spf.py
python3 test_earley_spf.py # -> ALL CHECKS PASSED
Key entry points in earley_spf.py:
g = EarleySPPF([Rule("E", ("E","+","E"), 1.0),
Rule("E", ("E","*","E"), 1.5),
Rule("E", ("x",), 0.7)], "E")
g.recognize(tokens) # bool; terminates on nullable/cyclic/left-recursive grammars
g.build_forest() # SPPF DAG, shared by node identity
g.count() # exact arbitrary-precision int (all 10^6 derivations)
g.count_mod(1_000_000_007) # exact count modulo a prime
g.kbest(5) # [(cost, key), ...] Viterbi order, deterministic ties
g.stats() # {'nodes': ..., 'edges': ...}
An independent brute-force enumerator is compared against the forest for small inputs. Results:
| Check | Result |
|---|---|
Nullable S→A B, A→ε\|a, B→ε\|a on a |
count = 2, matches brute force |
Direct left rec A→A a \| b on b a a |
recognized, count = 1 |
Indirect left rec A→B a\|b, B→A (± nullable) |
terminates, matches brute force |
| Dangling else | exactly 2 parses, mod count agrees |
E→E+E \| E*E \| x, 16 operands |
exact Catalan(15) = 9,694,845; forest only 574 nodes / 1134 edges |
Epsilon cycle A→B, B→A, B→ε |
recognized; count()/kbest() raise InfiniteForestError |
| Dead cycle (no base) | terminates, count = 0 |
| 30 random k-best trials vs brute force | all match |
| Tie-breaking | strictly increasing keys, lower rule index first, bit-identical reruns |
| Modulo consistency (n=2,5,8,12,16) | count_mod(p) == count % p |
Performance on the ambiguous arithmetic grammar (single run):
n=16 nodes=574 edges=1134 count=9,694,845 recognize=0.000s build=0.001s count=0.000s kbest=0.007s
n=20 nodes=878 edges=2018 count=1,767,263,190 recognize=0.002s build=0.001s count=0.000s kbest=0.016s
n=24 nodes=1246 edges=3270 count=343,059,613,650 recognize=0.001s build=0.002s count=0.001s kbest=0.031s
The SPPF stays polynomial while the derivation count grows exponentially, confirming no parse-tree enumeration occurs. SOLUTION.md contains the full analysis, complete source for both files, and all verification output.
# Evidence - Problem class: python-earley-shared-packed-forest-kbest - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-11T16:11:21.061Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Implement an Earley recognizer/parser that handles nullable nonterminals, epsilon cycles, and direct plus indirect left recursion without looping forever, and that stores every derivation in a shared packed forest so an ambiguous grammar with roughly 10^6 derivations is never expanded into a parse-tree enumeration. From that forest, return the k-best parses in Viterbi order under per-rule weights, deduplicating shared subtrees by identity while still reporting the exact derivation count (arbitrary precision) for the whole forest. Verify on the dangling-else grammar, an ambiguous arithmetic grammar, and a nullable-heavy grammar where the naive Earley completer diverges, asserting parse counts modulo a large prime and the correct tie-breaking order for the k-best list.", "environment": "python3", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "python-earley-shared-packed-forest-kbest", "provider": "openrouter", "solved_at": "2026-09-11T16:11:21.061Z", "version": "3.12"}The solution is complete and verified. The full self-contained markdown is written to ~/SOLUTION.md, alongside the runnable earley_spf.py and test_earley_spf.py.
A textbook Earley implementation fails on this problem class in four ways:
origin == end, so the completed item and the waiter it must advance live in the same chart set. A waiter inserted after the completion is processed is silently dropped, and if you instead re-enqueue naively, an epsilon cycle (A→B, B→A, B→ε) spins forever. Fix: process positions left-to-right and saturate each with an agenda, indexing waiters[pos][X] and completed_at[end][origin], and let a processed waiter advance against an already-present nullable completion at completed_at[pos][pos]. Each item is inserted once (bounded by |rules|·(n+1)²), so it always terminates.(rule, dot, origin) is added once and the chart reaches a finite fixed point (direct and indirect).E→E+E | E*E | x, 16 operands ⇒ Catalan(15) = 9,694,845 trees). Fix: a true SPPF — one symbol node per span plus one intermediate node per Earley item, with hyperedges per split point. Counting is linear DP over the DAG; shared sub-forests are node identities.(tropical cost, derivation key). Ties break on the preorder sequence of rule indices / split positions, which is total and hash-order independent. Genuine cycles ⇒ InfiniteForestError (truly infinite count).cd ~
python3 -m py_compile earley_spf.py test_earley_spf.py
python3 test_earley_spf.py # -> ALL CHECKS PASSED
Key entry points in earley_spf.py:
g = EarleySPPF([Rule("E", ("E","+","E"), 1.0),
Rule("E", ("E","*","E"), 1.5),
Rule("E", ("x",), 0.7)], "E")
g.recognize(tokens) # bool; terminates on nullable/cyclic/left-recursive grammars
g.build_forest() # SPPF DAG, shared by node identity
g.count() # exact arbitrary-precision int (all 10^6 derivations)
g.count_mod(1_000_000_007) # exact count modulo a prime
g.kbest(5) # [(cost, key), ...] Viterbi order, deterministic ties
g.stats() # {'nodes': ..., 'edges': ...}
An independent brute-force enumerator is compared against the forest for small inputs. Results:
| Check | Result |
|---|---|
Nullable S→A B, A→ε\|a, B→ε\|a on a |
count = 2, matches brute force |
Direct left rec A→A a \| b on b a a |
recognized, count = 1 |
Indirect left rec A→B a\|b, B→A (± nullable) |
terminates, matches brute force |
| Dangling else | exactly 2 parses, mod count agrees |
E→E+E \| E*E \| x, 16 operands |
exact Catalan(15) = 9,694,845; forest only 574 nodes / 1134 edges |
Epsilon cycle A→B, B→A, B→ε |
recognized; count()/kbest() raise InfiniteForestError |
| Dead cycle (no base) | terminates, count = 0 |
| 30 random k-best trials vs brute force | all match |
| Tie-breaking | strictly increasing keys, lower rule index first, bit-identical reruns |
| Modulo consistency (n=2,5,8,12,16) | count_mod(p) == count % p |
Performance on the ambiguous arithmetic grammar (single run):
n=16 nodes=574 edges=1134 count=9,694,845 recognize=0.000s build=0.001s count=0.000s kbest=0.007s
n=20 nodes=878 edges=2018 count=1,767,263,190 recognize=0.002s build=0.001s count=0.000s kbest=0.016s
n=24 nodes=1246 edges=3270 count=343,059,613,650 recognize=0.001s build=0.002s count=0.001s kbest=0.031s
The SPPF stays polynomial while the derivation count grows exponentially, confirming no parse-tree enumeration occurs. SOLUTION.md contains the full analysis, complete source for both files, and all verification output.
# Evidence - Problem class: python-earley-shared-packed-forest-kbest - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-11T16:11:21.061Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Implement an Earley recognizer/parser that handles nullable nonterminals, epsilon cycles, and direct plus indirect left recursion without looping forever, and that stores every derivation in a shared packed forest so an ambiguous grammar with roughly 10^6 derivations is never expanded into a parse-tree enumeration. From that forest, return the k-best parses in Viterbi order under per-rule weights, deduplicating shared subtrees by identity while still reporting the exact derivation count (arbitrary precision) for the whole forest. Verify on the dangling-else grammar, an ambiguous arithmetic grammar, and a nullable-heavy grammar where the naive Earley completer diverges, asserting parse counts modulo a large prime and the correct tie-breaking order for the k-best list.", "environment": "python3", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "python-earley-shared-packed-forest-kbest", "provider": "openrouter", "solved_at": "2026-09-11T16:11:21.061Z", "version": "3.12"}