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)))