ラベル 15パズル の投稿を表示しています。 すべての投稿を表示
ラベル 15パズル の投稿を表示しています。 すべての投稿を表示

2019年4月22日月曜日

15パズル

最近, 高木貞治先生の「数学小景」を眺めていたら, その中に「十五の駒遊び」という題で15パズルの話があった.

ところで, 最近知った15パズルの面白い解き方は, 福岡教育大学の藤本さんの提案する「回転型操作」によるものだ.

2件の論文がある.

情報処理学会研究報告にある「スライドパズルにおける回転型操作とアクセスビリティ」と, 近着の数式処理にある「Loop generatorによる15パズルの最適アルゴリズムとGod's numberについて」.

私は明解な手順には関心があるが, 最短手順には興味がないので, 後の論文は眺めた程度であった. 前の論文も解法は数式処理しシステムGAPを使って記述してあるので, GAPに馴染まぬ凡人にはとんと理解し得ぬ. 以下では私がSchemeで書いたプログラムの考え方を述べる.

この回転操作(英語ではloop generatorというらしい)は, 前回の15パズルのブログと似たやり方だが, 経路の単純なのが取り柄だ. 回転操作の前と後では, 空白は右下隅(15)に置くことにする. (イタリックの数字は場所の番地である.) 回転操作にはa, b, cがあり, それぞれ下の図の赤, 緑, 青の線のようにこまを回す. もちろん反対方向にも回す(回転数にマイナスを付けて示す). 例えばaを1回というと, 0にあったこまは4へ移動, 2回というと8へ移動する. bを1回では, 5のこまは9へ, 2回では13へ進む. 逆回転はは回数を負にする. aを-2回というと, 02へ行く.



回転経路が重なるのは, 図の7, 11, 13, 14の場所で, ここを経由して回転だけを使い, こまを任意の場所から他の任意の場所へ移すのである.
 
例えば5にあるこまを0へ移すには, aを4回して移動先013へ移動し, bを2回で移動元を13へ移し, aを-4回して最終目的地0へ動かす.

移動元と移動先が同じループに属しているといささか面倒だ. 例えば4から1へ移動するには,どちらもaのループにあるから, あらかじめ4を内側のループ, bかcのaと重ならない場所へ移動しておく. つまりaを3回で, 4のものを13へ; 次にbを-1回で9へ; 移動先の1は, a3回で8へ来ているから, 後2回で13へ進める. そこでbを1回行い, 4にあったものを13へ移す. それからaを-5回行うと, I1の位置へ行くわけである.

こういう手順が判明したから, aのループにある, 上端と左端にあるべき7個(4,3,2,1,,5,9,13)のこまを目的位置へ移す. この時はb, cのループの場所は自由に使ってよい. その後, bのループの5個(8,7,6,10,14)を揃える. この作業場所はcの10である. bが揃うと残りはcのループの10, 11, 14の番地に11, 12, 15のこまが残るだけになり, もとの配置のパリティが合っていれば, c1回かc-1回で完成する.

プログラムは後で示すことにし, 実際に走ったところを見よう. 下の図の左上(A)が最初の状態で, 空白はすでに右下15にある. 

こま4はbのループにあるから, (a 7)(b 2)(a -7)を行うと, (a 7)で4の目的地313へ行き, (b 2)で4が13へ行き, (a -7)で4が3へ移る. (B)になる.



こま3は元々は目的地2にあったのだが, 4の移動のとばっちりで0にいる. そこで(a 4)(b -1)(a -4)(a 6)(b 1)(a -6)を実行する. (a 4)で3を13へ, (b -1)で9へ, (a -4)でaループを一旦元へ戻す. 改めて2の位置を(a 6)で13へ運び, (b 1)で3をそこに置き, (a -6)で(C)のように3が収まる. (a -4) (a 6)と続けるのは無駄だが, プログラムが明瞭になるからこのままにしてある.

こま2は3に連られて目的地に来ていたからなにもしない. (D).

次は1の番だ. またaのループにあるから, (a 3)(b -1)(a -3)(a 4)(b 1)(a -4)の前半で9の位置に置き, 後半で0へ移す. (E).

5はaループだが同時にbループでもある. この場合は, bの中へ入れればよいが, 私のプログラムでは(b 2)で5へ入れることにしている. それから(a 3)(b 2)(a -3)で(F).

9は(a 1)(b -1)(a -1)(a 2)(b 1)(a -2)とする. (G).
 
次の13もa, cループだから, 一旦10へ移す. (c 1). その後(a 2)(c 1)(a -2) で(H). ここまででaループの7個が終わる. 以後はaループの7個には影響しないように注意.

(H)を見ると8はb, cループにあるから, (c -1)で10へ避難. それから(b 5)(c 1)(b -5). (I)になる.
 
7はcループにあるから簡単だ. 6の場所を(b 4)で14へ. それから(c 1)(b -4)で(J). 幸運にもついでに6, 10も揃うから(K),(L)も同じ図だ.

14はb, cループ上にいるから(c -1)で10へ引上げ, (b 1)(c 1)(b -1).

最後は11, 12, 15のcループ. 11が10にあれば何もしない. 11にあれば(c 1), 14にあれば(c -1)だ. (N).

全体の手は
(a 7)(b 2)(a -7);4
(a 4)(b -1)(a -4)(a 6)(b 1)(a -6);3 2
(a 3)(b -1)(a -3)(a 4)(b 1)(a -4);1
(b 2)(a 3)(b 2)(a -3);5
(a 1)(b -1)(a -1)(a 2)(b 1)(a -2);9
(c 1)(a 2)(c 1)(a -2);13
(c -1)(b 5)(c 1)(b -5);8
(b 4)(c 1)(b -4);7 6 10
(c -1)(b 1)(c 1)(b -1);14
(c -1);11 12 15

であった.
 
一方, Schemeによるプログラムはこんな具合いだ.
(define bs (range 0 16))

は盤面のこまのリスト. 
(define atab '(
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
(4 0 1 2 8 5 6 3 12 9 10 7 13 14 11)
(8 4 0 1 12 5 6 2 13 9 10 3 14 11 7)
(12 8 4 0 13 5 6 1 14 9 10 2 11 7 3)
(13 12 8 4 14 5 6 0 11 9 10 1 7 3 2)
(14 13 12 8 11 5 6 4 7 9 10 0 3 2 1)
(11 14 13 12 7 5 6 8 3 9 10 4 2 1 0)
(7 11 14 13 3 5 6 12 2 9 10 8 1 0 4)
(3 7 11 14 2 5 6 13 1 9 10 12 0 4 8)
(2 3 7 11 1 5 6 14 0 9 10 13 4 8 12)
(1 2 3 7 0 5 6 11 4 9 10 14 8 12 13)))
(define btab '(
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
(0 1 2 3 4 9 5 6 8 13 10 7 12 14 11)
(0 1 2 3 4 13 9 5 8 14 10 6 12 11 7)
(0 1 2 3 4 14 13 9 8 11 10 5 12 7 6)
(0 1 2 3 4 11 14 13 8 7 10 9 12 6 5)
(0 1 2 3 4 7 11 14 8 6 10 13 12 5 9)
(0 1 2 3 4 6 7 11 8 5 10 14 12 9 13)))
(define ctab'(
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14)
(0 1 2 3 4 5 6 7 8 9 14 10 12 13 11)
(0 1 2 3 4 5 6 7 8 9 11 14 12 13 10)))

はa, b, cそれぞれの回転で, 0,...のこまがどこへ行くかを示す. atab, つまりaの表は, 最初の行が(a 0)に対応. 同じ場所に留まる. 次の行(4 0 1...)はaの1回転で, 04へ, 10へ移ることを示す. 最後の行(atab[10])は-1回のものだ. b, cについても同様.
  
(define (rotate tab)
 (let ((b (make-list 16 0)))
  (do ((i 0 (+ i 1))) ((= i 15))
   (list-set! b (list-ref tab i) (list-ref bs i)))
  (set! bs (list-copy b)) 'ok))
(define (a n) (rotate (list-ref atab (modulo n 11))))
(define (b n) (rotate (list-ref btab (modulo n 7))))
(define (c n) (rotate (list-ref ctab (modulo n 3))))

(a n)がaのループをn回実行する. aの表から使うべき行をとりだし, rotateへ. rotateは16個のリストbを用意し, bs内のこまの番号をbへ移して最後にbをbsへ戻す.
(define ps0 '(4 3 2 1 5 9 13))
(define qs0 '(3 2 1 0 4 8 12))
(define ps1 '(8 7 6 10 14))
(define qs1 '(7 6 5 9 13))

ps0はaループのこまの番号, qs0はその目的地. ps1とqs1はbループのものである. 次がいよいよ解くプログラムである.
(define (solve2)
 (do ((i 0 (+ i 1))) ((= i 7))
  (let* ((p (list-ref ps0 i)) (q (list-ref qs0 i))
   (r (elemindex p bs)) )
   (if (not (= r q)) (begin (set! r
    (case r
     ((7) (b 2) 5)
     ((11) (c 1) 10)
     ((14) (c -1) 10)
     ((13) (b -2) 5)
     ((0 1 2 3 4 8 12) (let ((l (length (member r qs0))))
      (a l) (b -1) (a (- l)) 9))
     (else r)))
    (if (= r 10)
     (let ((l (- 8 i))) (a l) (c 1) (a (- l)))
     (let ((l (- 7 i))) (a l)
      (b (length (member r '(6 5 9)))) (a (- l))))))))

ここまでがaループのプログラムで, 7個についてdoループを回す. letへ来て, pはこまの番号, qは目的地, rは現在地である. r=qなら何もしない. そうでないならcase式へ来て, r=7なら(b 2)を実行, こまを5へ, という風に読む. rが0,1,2,3,4,8,12なら, rがqs0にある位置から後方の長さlを知り, (a l) (b -1) (a (- l))を行ない, こまを9へ移す. 上のいずれでもなければelseへ来て, 回転はせず, rはそのまま. 上のbeginの次の(set! r ...)でrを更新する.

結局移動するこまはcの10かbの5,6,9のどこかにいるから, それらの情報を使い, 目的地へ移動する.

bループのプログラムが以下だが, 説明は同様だ.
 (do ((i 0 (+ i 1))) ((= i 5))
  (let* ((p (list-ref ps1 i)) (q (list-ref qs1 i))
   (r (elemindex p bs)))
   (if (not (= r q)) (begin
    (case r
     ((11) (c 1))
     ((14) (c -1))
     ((7 6 5 9 13) (let ((l (length (member r qs1))))
      (b l) (c -1) (b (- l)))))
   (let ((l (- 5 i))) (b l) (c 1) (b (- l)))))))
 (case (elemindex 11 bs)
  ((11) (c 1))
  ((14) (c -1)))
(drawbs))

という次第で無事に並べ替られる. なかなか楽しい.

2019年2月4日月曜日

15パズル

先日のブログは15パズルを手で解く方法であった. 今回は計算機でやる話だ. 計算機による解法もいろいろあるが, とりあえずはプログラムを簡単にするという方針でいく.

下に14枚の図がある. これらはどれも4×4の15パズルの盤面の右下の3×3の部分を表している. その証拠はますの左上に斜体の数字で示す番地だ. まず図Aを見てほしい. 数字の代りに文字の変数でこまを示す. 空白のますには文字も書かない. 図Aの下にあるuldrはこれからこまを移動する方向である. 従ってまずgを上げる. hを左へ寄せる. eを下げる. gを右に寄せる. その結果が図Bだ. その矢印が示すように, 10,11,14,15の中でg,e,hが右回転したことになる. ほかのこまには影響しない.

この手順を右回転, 10,11,14,15の4ますを主戰場ということにする.

さて主戰場以外の場所のpを14に移動し(この時ほかのこまに影響があってもよい), 右回転し, 14のこまをpにもどすと(影響はもとに戻って), 15にあったものがpに, pが11に, 1115に移動したことになる.

p→11,11→15;15→p



どこかのこまを14に移動するには, 図C,Dに見るように, Cでurddluを実行する. するとDのように, 14,13,9,5,6のこまが左大回転する. gが2度のuで2段上に登るのは, 10に空白を残すためである.

ここで主戰場の右回転すると, Eになり, 先程の左大回転を逆回転するとFになって, f,e,hが回転する.

Gからの図では左大回転を2回実行し, 右回転, 右大回転2回で, d,e,hが回転することが分る. Kからの図は先に右大回転をすると, b,e,hが回転することを表す. ここには図がないが, Kの大回転を2回ずつ実行すると, a,e,hが回転することは容易に分る.

以上で5,6,9,13との交換はできたが, ほかの場所との交換は, 大回転の道順を工夫すればよいのも明らかだ. それが次の図である. 上の道順はルート0のつもりで, 左上のrt0である. 6のますに1, 7のますに2とあるのは, それぞれ右大回転を1回, 2回行うという意味だ. 913の2と1は左大回転の2回と1回を示す.

これを見れば, 主戰場の外のすべてのますを14へ移動する方法が分る.

   



これをまとめたのが以下で, 最初のrotは右回転, 続くrt0+, rt0-などはrt0の左大回転と右大回転である.
 
  
rot uldr
rt0+ urddlu
rt0- duuuld
rt1+ urrddllu
rt1- drrulld
rt2+ urrddluu
rt2- ddruuuld
rt3+ urdddlluru
rt3- dldrruuuld
rt4+ urrdddlluu
rt4- ddrruuulld


以下はpを11,15と交換する手順を, rtpで示したもので, 例えば左上の0との交換には, 上の図のrt4を4回転するから, rt4-が4回, その後, 右回転, 更にrt4+を4回を示している. rt10,11,14,15がないのは, そこが主戰場だからだ.

rt0 rt4- rt4- rt4- rt4- rot rt4+ rt4+ rt4+ rt4+
rt1 rt2- rt2- rt2- rot rt2+ rt2+ rt2+
rt2 rt2- rt2- rot rt2+ rt2+
rt3 rt3- rt3- rt3- rot rt3+ rt3+ rt3+
rt4 rt1- rt1- rt1- rot rt1+ rt1+ rt1+
rt5 rt0- rt0- rot rt0+ rt0+
rt6 rt0- rot rt0+
rt7 rt3- rt3- rot rt3+ rt3+
rt8 rt1+ rt1+ rt1+ rot rt1- rt1- rt1- 
rt9 rt0+ rt0+ rot rt0- rt0-
rt12 rt1+ rt1+ rot rt1- rt10
rt13 rt0+ rot rt0-


この準備が出来ると, ランダムな配置から元へ戻すことが可能になる. 前回のブログのランダムの例から戻す手順をやってみたのが以下だ.

まず左上0の直ぐ下がランダムな状態で, その下へ進んで(sp10)は空白を10へもっていく命令を実行したところである. 命令の後の1は時間順を表す.

この状態の右下15をみると9のこまがあり, これを8に戻したいから(rt8)を行う. (rt8)2の後のように, こま9は場所8へ移動している. そして10が15に来たから, 次は(rt9)を行う. そして10は9へ入る. 次は13だから(rt12), 次は12だから行先は主戰場の中だ. 従って(rot)をして主戰場の中を回転する. すると8が15に来る. そこで(rt7)を実行して8を7に入れた. これで左端の列は終り, 2列目へ.

2列目は順調に進行する. 3列目の中程, (rt0)で1を左上へ移動すると, 主戰場は11, 12, 15になり, rotしても様子は変らない. この場合はこまの並びで自分の場所にいないものを探す. するとこま4が1にいるから(rt1)を行い, 4を主戰場へ取込み, 1拍おいた後に3へ送る準備をする. それが時間番号でいうと, 18,19,20になる.

このように進んで, 22までくると, 主戰場以外はすべて揃ったので, 後は適当な回数の(rot)を実行し, 右下隅に12が来たら(l)(u)を行い, 最後を揃えて終わる. これをプログラムに書き直すのは簡単だ.

0             (rt2)7        (rot)14       (rot)21        
                              
  7  4 11  2    7  4  3  2   11  4  3  2    1 15  3  4
 15  1 14 __   15  1 14  8    5  1  7  8    5  6  7  8
 13 12 10  5    9 10 __ 11    9 10 __ 12    9 10 __ 11
  8  6  3  9   13  6 12  5   13 14 15  6   13 14 12  2
                                                 
(sp10)1          (rt4)8        (rt5)15      (rt1)22          
                                                 
  7  4 11  2    7  4  3  2   11  4  3  2    1  2  3  4
 15  1 14  5    5  1 14  8    5  6  7  8    5  6  7  8
 13 12 __ 10    9 10 __ 15    9 10 __  1    9 10 __ 15
  8  6  3  9   13  6 12 11   13 14 15 12   13 14 12 11
                                                 
(rt8)2          (rt0)9        (rot)16      (rot)23          
                                                 
  7  4 11  2   11  4  3  2   11  4  3  2    1  2  3  4
 15  1 14  5    5  1 14  8    5  6  7  8    5  6  7  8
  9 12 __ 13    9 10 __  7    9 10 __ 15    9 10 __ 12
  8  6  3 10   13  6 12 15   13 14 12  1   13 14 11 15
                                                 
(rt9)3          (rot)10        (rt0)17      (rot)24          
                                                 
  7  4 11  2   11  4  3  2    1  4  3  2    1  2  3  4
 15  1 14  5    5  1 14  8    5  6  7  8    5  6  7  8
  9 10 __ 12    9 10 __ 12    9 10 __ 11    9 10 __ 11
  8  6  3 13   13  6 15  7   13 14 12 15   13 14 15 12
                                                 
(rt12)4          (rt6)11        (rt1)18      (l) (u)25     
                                                 
  7  4 11  2   11  4  3  2    1 15  3  2    1  2  3  4
 15  1 14  5    5  1  7  8    5  6  7  8    5  6  7  8
  9 10 __  8    9 10 __ 14    9 10 __  4    9 10 11 12
 13  6  3 12   13  6 15 12   13 14 12 11   13 14 15 __
                                   
(rot)5          (rot)12        (rot)19        
                                   
  7  4 11  2   11  4  3  2    1 15  3  2
 15  1 14  5    5  1  7  8    5  6  7  8
  9 10 __  3    9 10 __ 15    9 10 __ 12
 13  6 12  8   13  6 12 14   13 14 11  4
                                   
(rt7)6          (rt13)13     (rt3)20        
                                   
  7  4 11  2   11  4  3  2    1 15  3  4
 15  1 14  8    5  1  7  8    5  6  7  8
  9 10 __  5    9 10 __  6    9 10 __  2
 13  6 12  3   13 14 12 15   13 14 11 12

最後に一言. これはプログラムをさぼるという方針のため, 実際にこまを動かす回数は膨大になっている. 人手向きではないことに注意が必要だ.

2019年2月2日土曜日

15パズル

15パズルというのは, Rubikキューブよりほぼ百年前に登場した, Rubikキューブと同じ置換パズルである. しかし, Rubikキューブにくらべれば, 解くのははるかに簡単である.

置換パズルについてはそのうち説明るすることにし, 15パズルは下の図のようなものだ. 1から15までの数を書いた正方形のこまが, 16枚収まる正方形の枠に入っている. 図のAのように左上から右下にかけて, 1から15のこまが並び, 最後が1箇所空白になっている. これを整列状態をいおう. 空白に隣接したこまは空白の方向へ滑らせて移動でき, 移動した後が空白になる.

図のBは12のこまを下へ移動したところ; Cは11を右へ移動したところである. このように空白へ隣のこまを移動することで, ランダムになったある状態(例えば図のD)から出発し, こまを上下左右に移動することを繰り返すだけで, 整列状態へ戻すパズルである. こまの色は整列した場所の色としては意味があるが, こま自身の色には意味はない.


  

少しやってみると, すぐに出来るようになるが, 難しい場所もある. 人手でやる場合の戻し方の要領を次の図に示す. こまを上へ動かす, 右へ動かすという代りに, 15パズルの世界ではu, rのように書く. 下, 左はd, lである.



Aはこまのある場所を示す番地の図だ. 番地は以下の文では斜体で書く.  こま1の場所が0なのはややこしいが, とにかくこのように番号をつける. まず0から15の場所から1のこまを探し出し, それを0へ移動してBにするのは簡単だ. 次に2のこまを1へ移動するとCになる. これもなんでもない.

3を2へ入れるのは多少問題で, その時4が3にあればめでたいが, 4以外のものがあるところへ4を入れようとすると, 3に影響が及ぶ. 解決法は3と4をひとまとめにして収めることである. Dのように4を2に入れ, 3には3以外のもの(今はそれをxとする)があるとして3を7に置く. 空白は6に移動しておく.

4を下げ, xを左へ, 3を上, 4を右に動かす. つまり図の下にあるように, dlurを実行すると, Eの図になる. 続いてxを下げ, 3を左, 4を上に動か  すとFのように3と4が収まるのである.

運悪く3の位置に3がある時は, 図のG,H,I,Jのように動かす. Gの下に示すdruを実行すると, Hの図になり, 4と3が縱に並ぶ. この4と3を離したいから, Hの下のuldrでI, 4と3の間にyが闖入した. その下のdluでJ, その下のurdでDの図になる.

この下の段も同じ要領で, 5,6,7,8が収まる. しかし次は9,10,11,12を揃えるのではなく, 9,13を3,4の方法で入れる. さらに10,14も同様にして入る. すると最後は10,11,14,15に11,12,15が残るので, 10に11が来るまでぐるぐる回せば完成である.

15パズルは整列状態からこまを適当に滑らせてランダムにしてみても, そうランダムにはならない. かといって全部のこまをとりだし,バラバラに箱詰めしても, 整列状態には戻らない配置になる可能性がある.


遊んでみるためには, 私の作った下の図のようなのがhttp://www.iijlab.net/~ew/15puz190202.html
にある.
 


空白の隣のこまをクリックすると, そのこまが空白の方向へ移動する. 下のClrをクリックすると整列状態になり, Ranをクリックするとランダムになる.