## common lispを紹介するプログラムの一例 #### nz_tcoder --- ## 目次 * はじめに * 数独リゾルバ * 比較してみる * まとめ --- ## はじめに lispはマイナーです。 * 原因は? もっと広めるには? * 噂は聞いていても、実際にプログラムを見る/触ることがない? * 記号処理、人工知能などのキーワードが敷居を高くしているかも? * リスト処理は何がうれしいか分からない? 他の言語で書かれたプログラムをlispで書くことで、lispの良さを伝えられないか。 --- ## 数独リゾルバ プログラム言語Ruby[1]のイントロダクション > コメントと空行を除くと、ちょうど129行のコードが残る。 > > ...(中略)... > > このサンプルは、Rubyのパワーと表現力をよく示していると思うがどうだろうか。 ではcommon lispで書くとどうなる? パワーと表現力は? --- ## 数独リゾルバ(概要) * possible: セル(row,col)について、配置可能な数字を求める。 ![数独リゾルバ](img/resolver1.png) --- ## 数独リゾルバ(概要) * scan: 1. 未設定の各セルに対しpossibleを呼ぶ(イテレータ:each_unknown利用) 1. possibleの結果(配置可能な数字)が一つであれば、その値をセルに設定する。 1. セルの設定ができなくなったら終了する。終了時には、possibleの結果が最も少ないセルのrow、col、配置可能な数字のリストを返す。 * resolve: 1. scanを呼び出す。 1. 数独が解けていなければ、配置可能な数字を順番に試す(再帰的にresolveを呼ぶ) --- ## 実行例(ruby:easy) ``` $ tail -5 sudoku.rb raise Impossible end end puts Sudoku.solve(Sudoku::Puzzle.new(ARGF.readlines)) # 一行追加 $ sed '/^ *$/d' sudoku.rb |sed '/^ *#/d' |wc 127 653 4977 ``` ー行追加して127行? ``` $cat easy.txt .5...4..7 8..72...1 ..6.8..3. 9.8..24.6 1...473.. 423659... 58.216793 .1759...4 6924.3815 $ ruby sudoku.rb < easy.txt 251364987 839725641 746981532 978132456 165847329 423659178 584216793 317598264 692473815 ``` -- ## 実行例(ruby:normal) ``` $ cat normal.txt .....7... 9....4... ..6.....1 .5..1.... 7.....48. ....6..2. ..1.5...2 48....... ......... $ ruby sudoku.rb < normal.txt 138627594 975134268 246985371 652418937 713592486 894763125 361859742 489271653 527346819 ``` --- ## common lisp版の方針 * オブジェクト指向(CLOS)は使わない。 * common lisp版イテレータの実装はしない。 * 数独の表現には多次元配列(Array)を用いる。 * (aref *配列* *i* *j* ...)
多次元でもOK * (row-major-aref *配列* *i*)
*i* は row-major order プログラムは https://github.com/nz-tcoder/kansai-lisp-2/tree/master/lisp -- ### row-major order
![row-major-1](img/row-major-1.png) ![row-major-2](img/row-major-2.png)
--- ## 実行例(lisp: easy) ``` $ sed '/^ *$/d' sudoku.lisp | sed '/^ *;/d' |wc 87 395 3660 CL-USER> easy ((0 5 0 0 0 4 0 0 7) (8 0 0 7 2 0 0 0 1) (0 0 6 0 8 0 0 3 0) (9 0 8 0 0 2 4 0 6) (1 0 0 0 4 7 3 0 0) (4 2 3 6 5 9 0 0 0) (5 8 0 2 1 6 7 9 3) (0 1 7 5 9 0 0 0 4) (6 9 2 4 0 3 8 1 5)) CL-USER> (sudoku easy) #2A((2 5 1 3 6 4 9 8 7) (8 3 9 7 2 5 6 4 1) (7 4 6 9 8 1 5 3 2) (9 7 8 1 3 2 4 5 6) (1 6 5 8 4 7 3 2 9) (4 2 3 6 5 9 1 7 8) (5 8 4 2 1 6 7 9 3) (3 1 7 5 9 8 2 6 4) (6 9 2 4 7 3 8 1 5)) ``` -- ## 実行例(lisp: normal) ``` CL-USER> normal ((0 0 0 0 0 7 0 0 0) (9 0 0 0 0 4 0 0 0) (0 0 6 0 0 0 0 0 1) (0 5 0 0 1 0 0 0 0) (7 0 0 0 0 0 4 8 0) (0 0 0 0 6 0 0 2 0) (0 0 1 0 5 0 0 0 2) (4 8 0 0 0 0 0 0 0) (0 0 0 0 0 0 0 0 0)) CL-USER> (sudoku normal) #2A((1 3 8 6 2 7 5 9 4) (9 7 5 1 3 4 2 6 8) (2 4 6 9 8 5 3 7 1) (6 5 2 4 1 8 9 3 7) (7 1 3 5 9 2 4 8 6) (8 9 4 7 6 3 1 2 5) (3 6 1 8 5 9 7 4 2) (4 8 9 2 7 1 6 5 3) (5 2 7 3 4 6 8 1 9)) ``` --- ## 比較してみる(0) #### each_unknown(ruby) ```ruby BoxOfIndex = [ 0,0,0,1,1,1,2,2,2,0,0,0,1,1,1,2,2,2,0,0,0,1,1,1,2,2,2, 3,3,3,4,4,4,5,5,5,3,3,3,4,4,4,5,5,5,3,3,3,4,4,4,5,5,5, 6,6,6,7,7,7,8,8,8,6,6,6,7,7,7,8,8,8,6,6,6,7,7,7,8,8,8 ].freeze def each_unknown 0.upto 8 do |row| 0.upto 8 do |col| index = row*9+col next if @grid[index] != 0 box = BoxOfIndex[index] yield row, col, box end end end ``` --- ## 比較してみる(0) #### unknown_cells(common lisp) ```lisp (defconstant +box-index-list+ (loop for n in '(0 3 6) append (loop repeat 3 append (loop repeat 3 for i from n append (loop repeat 3 collect i))))) (defun box-index-of (idx) (nth idx +box-index-list+)) (defun unknown-cells (pz) (loop repeat (array-total-size pz) for idx from 0 for cell = (row-major-aref pz idx) if (= cell 0) collect (list (floor idx 9) (mod idx 9) (box-index-of idx)))) ``` --- ## 比較してみる(1) #### possible(ruby) ```ruby def possible(row, col, box) AllDigits - (rowdigits(row) + coldigits(col) + boxdigits(box)) end def rowdigits(row) @grid[row*9,9] - [0] end def coldigits(col) result = [] # Start with an empty array col.step(80, 9) {|i| # Loop from col by nines up to 80 v = @grid[i] # Get value of cell at that index result << v if (v != 0) # Add it to the array if non-zero } result # Return the array end BoxToIndex = [0, 3, 6, 27, 30, 33, 54, 57, 60].freeze def boxdigits(b) i = BoxToIndex[b] [ @grid[i], @grid[i+1], @grid[i+2], @grid[i+9], @grid[i+10], @grid[i+11], @grid[i+18], @grid[i+19], @grid[i+20] ] - [0] end ``` --- ## 比較してみる(1) #### possible(common lisp) ``` lisp (defun possible (pz row column box) (set-difference +all-digits+ (remove-duplicates (append (row-digits pz row) (column-digits pz column) (box-digits pz box))))) (defun row-digits (pz row) (remove-zero (mapcar #'(lambda (x) (aref pz row x)) '(0 1 2 3 4 5 6 7 8)))) (defun column-digits (pz column) (remove-zero (mapcar #'(lambda (x) (aref pz x column)) '(0 1 2 3 4 5 6 7 8)))) (defconstant +box-index-list+ (loop for n in '(0 3 6) append (loop repeat 3 append (loop repeat 3 for i from n append (loop repeat 3 collect i))))) (defun box-digits (pz box) (remove-zero (mapcar #'(lambda (x) (row-major-aref pz x)) (loop for digit in +box-index-list+ for row-major-index from 0 if (= digit box) collect row-major-index)))) ``` --- ## 比較してみる(2) #### scan(ruby) ``` ruby def Sudoku.scan(puzzle) unchanged = false # This is our loop variable until unchanged unchanged = true rmin,cmin,pmin = nil min = 10 puzzle.each_unknown do |row, col, box| p = puzzle.possible(row, col, box) case p.size when 0 raise Impossible when 1 puzzle[row,col] = p[0] unchanged = false else if unchanged && p.size < min min = p.size rmin, cmin, pmin = row, col, p end end end end return rmin, cmin, pmin end ``` --- ## 比較してみる(2) #### scan(common lisp) ``` lisp (defun scan (pz) (loop named outer for cells = (unknown-places pz) do (loop for (r c b) in cells for digit-list = (possible pz r c b) if (null digit-list) do (signal 'impossible) else if (= (length digit-list) 1) do (setf (aref pz r c) (car digit-list)) and count r into update else minimize (length digit-list) into min and collect (list r c digit-list) into not-determined finally (if (zerop update) (return-from outer (values (find min not-determined :key #'(lambda (x) (length (third x)))) (= min 0))))))) ``` --- ## 比較してみる(3) #### solve(ruby) ``` ruby def Sudoku.solve(puzzle) puzzle = puzzle.dup r,c,p = scan(puzzle) return puzzle if r == nil p.each do |guess| puzzle[r,c] = guess begin return solve(puzzle) rescue Impossible next end end raise Impossible end ``` --- ## 比較してみる(3) #### solve(common lisp) ```lisp (defun solve (puzzle) (let ((pz (copy-array puzzle))) (multiple-value-bind (possible finish) (scan pz) (if finish pz (destructuring-bind (r c guess-list) possible (loop for guess in guess-list do (setf (aref pz r c) guess) (handler-case (return (solve pz)) (impossible ())) finally (signal 'impossible))))))) ``` --- ## まとめ * 87行プログラム * 手を抜いた部分もあったかもしれない * lispのインデント * '-'を変数名、関数名に使えるのは楽 * 他にない機能(destructuring-bind,loop,condition(cltl2),multiple-value-bindなど)を紹介できた -- ### scanの末尾を比較 ruby ``` if unchanged && p.size < min min = p.size rmin, cmin, pmin = row, col, p end end end end return rmin, cmin, pmin end ``` common lisp ``` (if (zerop update) (return-from outer (values (find min not-determined :key #'(lambda (x) (length (third x)))) (= min 0))))))) ``` -- ### possbileでの比較 ruby ``` def possible(row, col, box) AllDigits - (rowdigits(row) + coldigits(col) + boxdigits(box)) end ``` common lisp ``` (defun possible (pz row column box) (set-difference +all-digits+ (remove-duplicates (append (row-digits pz row) (column-digits pz column) (box-digits pz box))))) ``` --- ## 参考文献 1. プログラミング言語Ruby(David Flanagan,まつもとゆきひろ共著,オライリージャパン,
Example code(http://examples.oreilly.com/9780596516178/RPLExamples.tar.gz)) 1. Common Lisp Recipes(Edumnd Weitz著,Apress) 1. 実践Common Lisp(Peter Seibel著,オーム社)
(英語版 http://www.gigamonkeys.com/book/) 1. 数独問題集(http://www.sudokugame.org/)