LinkedIn Queens — Solver Plan
LinkedIn Queens — Solver Plan
A command-line solver for LinkedIn Queens puzzles implemented purely through heuristic propagation. The solver does not use backtracking. It either solves the board with the heuristics catalogued in HEURISTICS.html, or it reports the partially-solved state and exits with a non-zero status.
Input
The solver reads one board from standard input. The board is a sequence of lines, one line per row of the N × N grid. Each character of a line is a region letter, following the format in archive/boards/<id>.txt (no separators between cells, one character per cell).
Example 8×8 board (from archive/boards/1.txt):
BBPPPSSS
BRPRPOSS
BRPRPSSS
BRRRPGNS
BRRRPGNN
BRTRPGNN
TRTRPGGN
TTTTNNNN
Parsing notes:
- Lines are trimmed of trailing whitespace. Empty trailing lines are ignored.
- The board size
Nis taken from the width of the first non-empty line. All rows must have the same width, and the number of non-empty rows must equal the width. - The input is assumed valid per the invariants in QUEENS.md. Exactly
Ndistinct region letters appear, and each region is 4-connected.
Output (success)
When the heuristics converge to a complete solution, the solver writes two sections to standard output, separated by a blank line, and exits with status 0.
1. Solved board
One line per row, using:
♛(U+265B, BLACK CHESS QUEEN) for each queen..for every other cell.
Example (for a hypothetical 7×7 solution):
...♛...
♛......
..♛....
....♛..
......♛
.♛.....
.....♛.
2. Heuristic counters
A list of every named heuristic with the number of times it fired during the solve. Zeros are included so that the report is always the same shape. A heuristic “fires” once per pass iteration in which its effect marked at least one new cell dead or placed a queen. Pattern matches that produce no new deduction do not count. The proposed output format is one heuristic per line, name left-padded to a fixed width, followed by the integer count:
Queen-adjacent kill : 7
Single-cell region : 2
Single-cell row / column : 0
Region-confined-to-line : 3
Line-confined-to-region : 1
Polyomino: domino : 1
...
Polyomino: T-hexomino : 0
N-regions-in-N-lines : 2
Placement-forces-contradiction : 0
Output (failure)
If the heuristics cannot complete the board, the solver writes the partial state (single section, no counter table) and exits with status 1. The partial state uses:
♛(U+265B) for placed queens.⨯(U+2A2F) for dead cells..for live cells (cells that are neither queen nor dead).
Example 7×7 partial state:
⨯⨯⨯♛⨯⨯⨯
♛⨯⨯⨯⨯⨯⨯
⨯⨯.⨯...
⨯..⨯...
⨯..⨯...
⨯..⨯...
⨯..⨯...
Heuristic catalog and counters
Thirty-one counters in total. Each counter is incremented once per successful application of its rule, meaning one increment per pass iteration in which the rule marked at least one new cell dead or placed a queen. Counters are not scaled by the number of cells affected.
Baseline rules (five counters)
| Counter name | Heuristic |
|---|---|
queen_adjacent_kill |
Queen-adjacent kill |
single_cell_region |
Single-cell region |
single_cell_line |
Single-cell row / column |
region_confined_to_line |
Region-confined-to-line |
line_confined_to_region |
Line-confined-to-region |
Polyomino shape rules (twenty-four counters)
One counter per ##### heading under ### Polyomino in HEURISTICS.md that carries an actual deduction. The I-tetromino, I-pentomino, and I-hexomino rules have no polyomino-specific deduction (their effect is handled entirely by region-confined-to-line), so they do not get their own counters.
| Size | Shape counters |
|---|---|
| 2 (domino) | polyomino_domino |
| 3 (tromino) | polyomino_i_tromino, polyomino_l_tromino |
| 4 (tetromino) | polyomino_t_tetromino, polyomino_l_tetromino, polyomino_s_tetromino |
| 5 (pentomino) | polyomino_l_pentomino, polyomino_f_pentomino, polyomino_n_pentomino, polyomino_t_pentomino, polyomino_u_pentomino, polyomino_v_pentomino, polyomino_w_pentomino |
| 6 (hexomino) | polyomino_z_with_tab_hexomino, polyomino_l_hexomino, polyomino_n_hexomino, polyomino_long_n_hexomino, polyomino_c_hexomino, polyomino_s_block_hexomino, polyomino_f_ext_a_hexomino, polyomino_n_ext_a_hexomino, polyomino_t_ext_hexomino, polyomino_w_ext_hexomino, polyomino_t_hexomino |
Pigeonhole rules (one counter)
| Counter name | Heuristic |
|---|---|
n_regions_in_n_lines |
Combined counter for N-regions-in-N-rows and N-regions-in-N-columns. Incremented once per pass iteration in which either variant marked a new cell dead. |
Placement-forces-contradiction (one counter)
| Counter name | Heuristic |
|---|---|
placement_forces_contradiction |
Incremented once per pass iteration in which the rule marked at least one candidate cell dead. Because the driver invokes this rule only after the cheap-tier rules have reached a fixed point (see “Solver control flow” below), this counter records how many times the expensive tier was needed to make progress. |
Solver control flow
- Read and parse stdin into a
regionsgrid. DeriveN. - Initialize
queensas the empty set anddeadas all-false. - Build the static precomputed tables described in HEURISTICS.md (per-cell kill list, canonical-polyomino-to-dead-offsets hash, bipartite incidence graphs for the N-regions rules, per-cell kill-mask bitmasks for the placement-forces-contradiction rule).
- Initialize the incremental data structures (live-cell counts, live-cell sets, distinct-rows/cols per region, distinct-regions per row/col, per-region dirty flag, dirty-and-small region set, per-region live-mask bitmasks, N-regions dirty flags,
h2_dirtyflag for the placement-forces-contradiction rule). - Run one initial sweep of the cheap-tier rules so that any deductions visible from the start state fire before the main loop.
- Main loop, two tiers:
- Cheap tier: repeatedly invoke every cheap rule in the order shown below until a full pass leaves
deadandqueensunchanged. - Expensive tier: if the cheap tier reached a fixed point with
|queens| < N, invokeapply_placement_forces_contradictiononce. - If the expensive tier marked any cell dead, loop back to the cheap tier. Otherwise exit the main loop.
- Cheap tier: repeatedly invoke every cheap rule in the order shown below until a full pass leaves
- After the main loop: if
|queens| == N, emit the success output. Otherwise emit the failure output and exit1.
Cheap-tier rule order within each pass:
- Baseline rules (likely to fire most often).
- Polyomino shape rules (iterate the dirty-and-small region set once, look up each region’s live-cell canonical form in the shared polyomino-to-dead-offsets table).
- N-regions-in-N-rows and N-regions-in-N-columns (skipped in
O(1)when their dirty flags are clear).
With the precomputed structures in place each cheap-tier rule costs O(B) per pass (or O(B²) for a dirty N-regions pass), so cheap-tier ordering is for clarity rather than performance. The expensive tier is the placement-forces-contradiction rule, which runs O(B²) per dirty pass but with tight bitmask constants several times larger than the baseline rules. Running it only after the cheap tier stops firing keeps the common case fast and still unlocks the puzzles that need the stronger deduction.
Implementation notes
- Language: Rust, shipped as a Cargo crate at the project root.
- Crate layout:
Cargo.tomlandsrc/at the project root.src/main.rsis the binary entry point. It reads stdin, writes stdout, and sets its exit status (0on success,1on failure).src/lib.rsexposes the core solver types and functions so that unit tests can exercise the solver directly.- Module suggestions under
src/:board.rs(parsing, region indexing),state.rs(live-cell counts, sets, dirty flags, bitmask live-masks and kill-masks, undo log),polyomino.rs(canonical-form hashing and dead-offset table),nregions.rs(bipartite graph plus Dulmage–Mendelsohn decomposition),rules.rs(individual rule functions, each returning a bool for “fired this pass”; the cheap-tier rules plus the expensiveapply_placement_forces_contradiction),counters.rs(the 31 named counters), andoutput.rs(success and failure formatters).
- Running:
cargo run --release < archive/boards/1.txtsolves a single board. - Testing: unit tests only. Each module under
src/carries its own#[cfg(test)] mod testsblock covering the canonical-form builder, the bipartite matching and decomposition, each individual rule’s fire-and-effect behaviour on small crafted boards, and the I/O formatters. - External crates: keep dependencies light. Standard library is enough for I/O and data structures. A small bipartite-matching helper can live in-tree rather than pulling a crate.
- Unicode: the solver’s output is UTF-8 by default in Rust. Terminals that do not render
♛or⨯will show replacement characters.