2020年3月10日火曜日

数独プログラム

 「 あらゆる数独パズルを解く」はPeter Norvigなのに、Pythonで書かれている。
 じゃあというわけでelispで書いてみた。

(2) ある値に対し、ユニットの中で置ける場所が1つしかないなら、その値をそこに置く。

 は、うまく動かなかったのでコメントアウトしているけど。
 それでも「 あらゆる数独パズルを解く」のページの問題はいちおう解けたからだいじょうぶだと思うけど。
 まぁ、よいか。

( defun  test ()
  ( progn (sudoku-init)
(sudoku-set  "
 . . 5 |3 . . |. . . 
 8 . . |. . . |. 2 . 
 . 7 . |. 1 . |5 . . 
 ------+------+------
 4 . . |. . 5 |3 . . 
 . 1 . |. 7 . |. . 6 
 . . 3 |2 . . |. 8 . 
 ------+------+------
 . 6 . |5 . . |. . 9 
 . . 4 |. . . |. 3 . 
 . . . |. . 9 |7 . . 
 ")
       (sudoku-solve)
       (sudoku-print)))

(benchmark 1 '(test))
| 1 4 5 | 3 2 7 | 6 9 8 |
| 8 3 9 | 6 5 4 | 1 2 7 |
| 6 7 2 | 9 1 8 | 5 4 3 |
|-------+-------+-------|
| 4 9 6 | 1 8 5 | 3 7 2 |
| 2 1 8 | 4 7 3 | 9 5 6 |
| 7 5 3 | 2 9 6 | 4 8 1 |
|-------+-------+-------|
| 3 6 7 | 5 4 2 | 8 1 9 |
| 9 8 4 | 7 6 1 | 2 3 5 |
| 5 2 1 | 8 3 9 | 7 6 4 |
 "Elapsed time: 0.179490s (0.093774s in 1 GCs)"

 ベンチマークもそこそこ。

;; -*- coding: utf-8; lexical-binding: t -*-
;;
;; 数独(sudoku.el)
;;
(require 'cl)
(require 'org)
(require 'dash)
(defun sudoku-combination-of-list (list1 list2)
"list1の要素とlist2の要素を組み合わせた結果を出力する。"
(-table-flat 'concat list1 list2))
(defun sudoku-table-convert (string)
"文字列をtableに変換する。"
(with-temp-buffer
(let ((w (replace-regexp-in-string "^\n" "" string)))
(insert w)
(org-table-convert-region (point-min)(point-max))
(goto-char (point-min))
(next-line 2)
(org-ctrl-c-minus)
(next-line 4)
(org-ctrl-c-minus)
(buffer-substring-no-properties (point-min)(point-max)))))
(let* ((tate '("1" "2" "3" "4" "5" "6" "7" "8" "9"))
(yoko '("A" "B" "C" "D" "E" "F" "G" "H" "I"))
(tate-group (-partition 3 tate))
(yoko-group (-partition 3 yoko))
(keys (sudoku-combination-of-list tate yoko))
(hash (make-hash-table :size 81 :test 'equal)))
(defun sudoku-hash ()
hash)
(defun sudoku-keys() keys)
(defun sudoku-init ()
"初期化処理"
(loop for x in keys ; 初期設定
do (puthash x "123456789" hash)))
(sudoku-init) ; 最初の初期化
(defun sudoku-units (key)
"KEYはマス目の場所をあらわす。pearになるリストを返す。"
(let* ((tate-key (substring key 0 1))
(yoko-key (substring key 1 2))
(tate (loop for x in tate-group
when (cl-find tate-key x :test #'string=)
collect x))
(yoko (loop for x in yoko-group
when (cl-find yoko-key x :test #'string=)
collect x)))
(sudoku-combination-of-list (car tate)(car yoko))))
(defun sudoku-tate (key)
(let ((tate-key (substring key 0 1)))
(sudoku-combination-of-list (list tate-key) yoko)))
(defun sudoku-yoko (key)
(let ((yoko-key (substring key 1 2)))
(sudoku-combination-of-list tate (list yoko-key))))
(defun sudoku-cross (key)
"十字のpearになるリストを返す。"
(-concat (sudoku-yoko key)(sudoku-tate key)))
(defun sudoku-peares (key)
"KEYのpearになるリストを返す。"
(let* ((peares (-concat (sudoku-units key)
(sudoku-cross key)))
(peares (remove-if (lambda(x)(equal x key)) peares)))
(delete-dups peares)))
(cl-defun sudoku-print (&optional (hash hash))
"hashテーブルの中身をバッファに出力する。"
(let* ((w (loop for x in keys
collect (gethash x hash)))
(w (-partition 9 w))
(w (loop for x in w
collect
(mapconcat 'concat (loop for y in (-partition 3 x)
collect
(mapconcat 'concat y " ")) "\t"))))
(insert (sudoku-table-convert (loop for x in w
concat (concat x "\n"))))))
(defun sudoku-value-overwrite (key value hash)
"hashのkeyの値をvalueにする。"
(let ((other-values (replace-regexp-in-string value "" (gethash key hash))))
(not (loop for x in (cl-concatenate 'list other-values)
unless (sudoku-value-minus key (char-to-string x) hash)
return t))))
(defun sudoku-value-minus (key value hash)
"hashのkeyの値からvalueを削除する。"
(let* ((w (gethash key hash))
(rtn (cond ((not (search value w)) w)
((search value w)(let ((w2 (replace-regexp-in-string value "" w)))
(setf (gethash key hash) w2)
(case (length w2)
(0 nil)
(1 (not (loop for x in (sudoku-peares key)
unless (sudoku-value-minus x w2 hash)
return t)))
(t t)))))))
rtn
;; (when rtn
;; (not (loop for unit in (list (sudoku-tate key)(sudoku-yoko key)(sudoku-units key))
;; unless (let ((w (loop for x in unit
;; when (search value (gethash x hash))
;; collect x)))
;; (case (length w)
;; (0 (return nil))
;; (1 (sudoku-value-overwrite (car w) value hash))
;; (t t)))
;; return t)))
))
(defun sudoku-set (string)
(loop for x in keys
for y in (sudoku-conv string)
when (string= (replace-regexp-in-string "\[1-9\]" "" y) "")
collect (sudoku-value-overwrite x y hash)))
(defun sudoku-conv (string)
(let* ((w (replace-regexp-in-string "\[^0-9.\]" " " string))
(w (mapconcat 'char-to-string (cl-concatenate 'string w) " ")))
(split-string w)))
(defun sudoku-min (hash)
(loop for x in keys
unless (= (length (gethash x hash)) 1)
minimize (length (gethash x hash))))
(defun sudoku-complete-p (hash)
(let ((w (loop for x in keys
when (= (length (gethash x hash)) 1)
count x)))
(when (= 81 w)
t)))
(defun sudoku-solve-run (hash)
(if (sudoku-complete-p hash)
hash
(let* ((min (sudoku-min hash))
(key (loop for x in keys
when (= (length (gethash x hash)) min)
do (return x))))
(loop for y in (cl-concatenate 'list (gethash key hash))
do (let ((w (copy-hash-table hash))
(x (char-to-string y)))
(when (sudoku-value-overwrite key x w)
(when-let (rtn (sudoku-solve-run w))
(return rtn))))))))
(defun sudoku-solve()
(when-let (w (sudoku-solve-run (copy-hash-table hash)))
(setq hash w))))
(defun test ()
(progn (sudoku-init)
(sudoku-set "
. . 5 |3 . . |. . .
8 . . |. . . |. 2 .
. 7 . |. 1 . |5 . .
------+------+------
4 . . |. . 5 |3 . .
. 1 . |. 7 . |. . 6
. . 3 |2 . . |. 8 .
------+------+------
. 6 . |5 . . |. . 9
. . 4 |. . . |. 3 .
. . . |. . 9 |7 . .
")
;; (sudoku-set "
;; 8 5 . |. . 2 |4 . .
;; 7 2 . |. . . |. . 9
;; . . 4 |. . . |. . .
;; ------+------+------
;; . . . |1 . 7 |. . 2
;; 3 . 5 |. . . |9 . .
;; . 4 . |. . . |. . .
;; ------+------+------
;; . . . |. 8 . |. 7 .
;; . 1 7 |. . . |. . .
;; . . . |. 3 6 |. 4 .
;; ")
(sudoku-solve)
(sudoku-print)))
view raw sudoku.el hosted with ❤ by GitHub

2020年3月6日金曜日

dash.el(2)

 たとえば、(1 2 3)と(4 5 6)を組み合わせたい。

((1 . 4) (2 . 4) (3 . 4) (1 . 5) (2 . 5) (3 . 5) (1 . 6) (2 . 6) (3 . 6))

 みたいな感じに。
 そこで

( defun  y-combination-of-list (list1 list2)
   "list1の要素とlist2の要素を組み合わせた結果をconsして出力する。"
  ( loop for x in list1
        collect ( loop for y in list2
                      collect (cons x y))))

 というのをでっちあげた。でもこれだと結果は

(((1 . 4) (1 . 5) (1 . 6)) ((2 . 4) (2 . 5) (2 . 6)) ((3 . 4) (3 . 5) (3 . 6)))

 という風になってしまう。
 これを入れ子のリストではなく、フラットなリストにしたい。
 それで「 dash.el」の「-flatten」が必要だったのだけど。

(-flatten '(((1 . 4) (1 . 5) (1 . 6)) ((2 . 4) (2 . 5) (2 . 6)) ((3 . 4) (3 . 5) (3 . 6))))
((1 . 4) (1 . 5) (1 . 6) (2 . 4) (2 . 5) (2 . 6) (3 . 4) (3 . 5) (3 . 6))

 よくよく考えたら「dash.el」に、そもそもそんな関数があるんじゃないか? つらつら「 https://github.com/magnars/dash.el#functions」を見ていたら。

(-table-flat 'cons '(1 2 3) '(4 5 6))
((1 . 4) (2 . 4) (3 . 4) (1 . 5) (2 . 5) (3 . 5) (1 . 6) (2 . 6) (3 . 6))

 あった。

2020年3月5日木曜日

dash.el

 どのパッケージがきっかけでインストールされたのか、わからないけれど、「dash.el」がインストールされている。

;;; dash.el --- A modern list library for Emacs

 だそうだ。
 「-flatten」というのがあってお気に入り。

(-flatten '((1 2 3)(4 5 6)))
(1 2 3 4 5 6)

 で、素の ABCLを使っていて「flatten」が必要になった。
 「alexandria」をインストールすれば、いいんだろうが、諸事情というものがある。
 「dash.el」の「-flatten」が使えないもんだろうか。
 「–map」「–mapcat」「-mapcat」「-flatten」を喰わせてみたところ。

(-flatten '((1 2 3)(4 5 6)))
(1 2 3 4 5 6)

 動いた。

2020年3月3日火曜日

async.el

 helmをインストールすると、もれなくasync.elがついてくる。
 Emacsの非同期ライブラリである。
 バックグラウンドでEmacsをもうひとつ、立ち上げてそちらで処理させる、というもの。

( setq wrk (async-start
           ( lambda ()
             222)))
-- 割愛 --

(async-get wrk)
222

 システムモニタを立ち上げておくと、Emacsがもうひとつ、起動されるのがわかる。
 100個とか、無理だろうけど 1

( setq wrk ( loop for x from 1 to 10
                collect (async-start
                         `( lambda ()
                            ,x))))
-- 割愛 --

( loop for x in wrk
      collect (async-get x))
(1 2 3 4 5 6 7 8 9 10)

 カンマを使えば、変数の中身を展開できるけど、さすがにクロージャーでデータを渡すのは無理だろうなぁ。別プロセスだもんなぁ。スレッドならできるのだろうけど。
 ——と思ったけれど、やってみたらできた。
 驚いた。

( setq lexical-binding t)
t

( defun  wrk (x)
  ( lambda()
    x))
wrk

( setq wrk ( loop for x from 1 to 10
                collect (async-start (wrk x))))
-- 割愛 --

( loop for x in wrk
      collect (async-get x))
(1 2 3 4 5 6 7 8 9 10)

 なんで?

(wrk 1)
(closure ((x . 1) t) nil x)

 ああ、クロージャーはリストに展開されるんだ。
 なるほど。それならpipe渡しでも可能だ。あれ? ということはelispだからできるってこと?
 ほえぇ。elispはあなどれない。

Footnotes:

1

Emacs for Windowsでは10個でエラーになった。