Function: 5x5-solver
5x5-solver is a byte-compiled function defined in 5x5.el.gz.
Signature
(5x5-solver GRID)
Documentation
Return a list of solutions for GRID.
Given some grid GRID, the returned a list of solution LIST is sorted from least Hamming weight to greatest one.
LIST = (SOLUTION-1 ... SOLUTION-N)
Each solution SOLUTION-I is a cons cell (HW . G) where HW is the Hamming weight of the solution --- ie the number of strokes to achieve it --- and G is the grid of positions to click in order to complete the 5x5.
Solutions are sorted from least to greatest Hamming weight.
Source Code
;; Defined in /usr/src/emacs/lisp/play/5x5.el.gz
(defun 5x5-solver (grid)
"Return a list of solutions for GRID.
Given some grid GRID, the returned a list of solution LIST is
sorted from least Hamming weight to greatest one.
LIST = (SOLUTION-1 ... SOLUTION-N)
Each solution SOLUTION-I is a cons cell (HW . G) where HW is the
Hamming weight of the solution --- ie the number of strokes to
achieve it --- and G is the grid of positions to click in order
to complete the 5x5.
Solutions are sorted from least to greatest Hamming weight."
(require 'calc-ext)
(cl-flet ((5x5-mat-mode-2
(a)
(math-map-vec
(lambda (y)
(math-map-vec
(lambda (x) `(mod ,x 2))
y))
a)))
(let* (calc-command-flags
(grid-size-squared (* 5x5-grid-size 5x5-grid-size))
;; targetv is the vector the origin of which is org="current
;; grid" and the end of which is dest="all ones".
(targetv
(5x5-log
"b"
(let (
;; org point is the current grid
(org (calcFunc-arrange (5x5-grid-to-vec grid)
1))
;; end point of game is the all ones matrix
(dest (calcFunc-cvec '(mod 1 2) grid-size-squared 1)))
(math-sub dest org))))
;; transferm is the transfer matrix, ie it is the 25x25
;; matrix applied every time a flip is carried out where a
;; flip is defined by a 25x1 Dirac vector --- ie all zeros
;; but 1 in the position that is flipped.
(transferm
(5x5-log
"a"
;; transfer-grid is not a play grid, but this is the
;; transfer matrix in the format of a vector of vectors, we
;; do it this way because random access in vectors is
;; faster. The motivation is just speed as we build it
;; element by element, but that could have been created
;; using only Calc primitives. Probably that would be a
;; better idea to use Calc with some vector manipulation
;; rather than going this way...
(5x5-grid-to-vec (let ((transfer-grid
(let ((5x5-grid-size grid-size-squared))
(5x5-make-new-grid))))
(dotimes (i 5x5-grid-size)
(dotimes (j 5x5-grid-size)
;; k0 = flattened flip position corresponding
;; to (i, j) on the grid.
(let* ((k0 (+ (* 5 i) j)))
;; cross center
(5x5-set-cell transfer-grid k0 k0 t)
;; Cross top.
(and
(> i 0)
(5x5-set-cell transfer-grid
(- k0 5x5-grid-size) k0 t))
;; Cross bottom.
(and
(< (1+ i) 5x5-grid-size)
(5x5-set-cell transfer-grid
(+ k0 5x5-grid-size) k0 t))
;; Cross left.
(and
(> j 0)
(5x5-set-cell transfer-grid (1- k0) k0 t))
;; Cross right.
(and
(< (1+ j) 5x5-grid-size)
(5x5-set-cell transfer-grid
(1+ k0) k0 t)))))
transfer-grid))))
;; TODO: this is hard-coded for grid-size = 5, make it generic.
(transferm-kernel-size
(if (= 5x5-grid-size 5) 2
(error "Transfer matrix rank not known for grid-size != 5")))
;; TODO: this is hard-coded for grid-size = 5, make it generic.
;;
;; base-change is a 25x25 matrix, where topleft submatrix
;; 23x25 is a diagonal of 1, and the two last columns are a
;; base of kernel of transferm.
;;
;; base-change must be by construction invertible.
(base-change
(5x5-log
"p"
(let ((id (5x5-mat-mode-2 (calcFunc-diag 1 grid-size-squared))))
(setcdr (last id (1+ transferm-kernel-size))
(cdr (5x5-mat-mode-2
'(vec (vec 0 1 1 1 0 1 0 1 0 1 1 1 0 1
1 1 0 1 0 1 0 1 1 1 0)
(vec 1 1 0 1 1 0 0 0 0 0 1 1 0 1
1 0 0 0 0 0 1 1 0 1 1)))))
(calcFunc-trn id))))
(inv-base-change
(5x5-log "invp"
(calcFunc-inv base-change)))
;; B:= targetv
;; A:= transferm
;; P:= base-change
;; P^-1 := inv-base-change
;; X := solution
;; B = A * X
;; P^-1 * B = P^-1 * A * P * P^-1 * X
;; CX = P^-1 * X
;; CA = P^-1 * A * P
;; CB = P^-1 * B
;; CB = CA * CX
;; CX = CA^-1 * CB
;; X = P * CX
(ctransferm
(5x5-log
"ca"
(math-mul
inv-base-change
(math-mul transferm base-change)))); CA
(ctarget
(5x5-log
"cb"
(math-mul inv-base-change targetv))); CB
;; (row-1 (math-make-intv 3 1 transferm-kernel-size)) ; 1..2
(row-2 (math-make-intv 1 transferm-kernel-size
grid-size-squared)); 3..25
(col-1 (math-make-intv 3 1 (- grid-size-squared
transferm-kernel-size))); 1..23
;; (col-2 (math-make-intv 1 (- grid-size-squared
;; transferm-kernel-size)
;; grid-size-squared)) ; 24..25
;; (ctransferm-1-: (calcFunc-mrow ctransferm row-1))
;; (ctransferm-1-1 (calcFunc-mcol ctransferm-1-: col-1))
;; By construction ctransferm-:-2 = 0, so ctransferm-1-2 = 0
;; and ctransferm-2-2 = 0.
;;(ctransferm-1-2 (calcFunc-mcol ctransferm-1-: col-2))
(ctransferm-2-: (calcFunc-mrow ctransferm row-2))
(ctransferm-2-1
(5x5-log
"ca_2_1"
(calcFunc-mcol ctransferm-2-: col-1)))
;; By construction ctransferm-2-2 = 0.
;;
;;(ctransferm-2-2 (calcFunc-mcol ctransferm-2-: col-2))
;; (ctarget-1 (calcFunc-mrow ctarget row-1))
(ctarget-2 (calcFunc-mrow ctarget row-2))
;; ctarget-1(2x1) = ctransferm-1-1(2x23) *cx-1(23x1)
;; + ctransferm-1-2(2x2) *cx-2(2x1);
;; ctarget-2(23x1) = ctransferm-2-1(23x23)*cx-1(23x1)
;; + ctransferm-2-2(23x2)*cx-2(2x1);
;; By construction:
;;
;; ctransferm-1-2 == zeros(2,2) and ctransferm-2-2 == zeros(23,2)
;;
;; So:
;;
;; ctarget-2 = ctransferm-2-1*cx-1
;;
;; So:
;;
;; cx-1 = inv-ctransferm-2-1 * ctarget-2
(cx-1 (math-mul (calcFunc-inv ctransferm-2-1) ctarget-2))
;; Any cx-2 can do, so there are 2^{transferm-kernel-size} solutions.
(solution-list
;; Within solution-list each element is a cons cell:
;;
;; (HW . SOL)
;;
;; where HW is the Hamming weight of solution, and SOL is
;; the solution in the form of a grid.
(sort
(cdr
(math-map-vec
(lambda (cx-2)
;; Compute `solution' in the form of a 25x1 matrix of
;; (mod B 2) forms --- with B = 0 or 1 --- and
;; return (HW . SOL) where HW is the Hamming weight
;; of solution and SOL a grid.
(let ((solution (math-mul
base-change
(calcFunc-vconcat cx-1 cx-2)))); X = P * CX
(cons
;; The Hamming Weight is computed by matrix reduction
;; with an ad-hoc operator.
(math-reduce-vec
;; (cadadr '(vec (mod x 2))) => x
(lambda (r x) (+ (if (integerp r) r (cadadr r))
(cadadr x)))
solution); car
(5x5-vec-to-grid
(calcFunc-arrange solution 5x5-grid-size));cdr
)))
;; A (2^K) x K matrix, where K is the dimension of kernel
;; of transfer matrix --- i.e. K=2 in if the grid is 5x5
;; --- for I from 0 to K-1, each row rI correspond to the
;; binary representation of number I, that is to say row
;; rI is a 1xK vector:
;; [ n{I,0} n{I,1} ... n{I,K-1} ]
;; such that:
;; I = sum for J=0..K-1 of 2^(n{I,J})
(let ((calc-number-radix 2)
(calc-leading-zeros t)
(calc-word-size transferm-kernel-size))
(math-map-vec
(lambda (x)
(cons 'vec
(mapcar (lambda (x) `(vec (mod ,(logand x 1) 2)))
(substring (math-format-number x)
(- transferm-kernel-size)))))
(calcFunc-index (math-pow 2 transferm-kernel-size) 0))) ))
;; Sort solutions according to respective Hamming weight.
(lambda (x y) (< (car x) (car y)))
)))
(message "5x5 Solution computation done.")
solution-list)))