数独をスプレッドシートの数式だけで解きたい

皆さんはパズルは好きですか?
私が好きなパズルの一つに「数独」があります。
9×9のマスに縦・横・ブロック内で被りが無いよう数字を埋めるアレで、
「ナンプレ」とも呼ばれますね。

何となく、スプレッドシートで解けないか考えたくなりました。
何となく、GASは使用せず数式のみでの実装を目指します。
今回はそんな内容です。

作成したもの

早速ですが、作成したシートをご紹介。
完成度としては60点で、簡単な問題なら解いてくれます。

説明の為に、解ききれない難しい問題を例に出しています。
最後にキレイに解ける例もお見せします。

ざっくり説明すると、
問題シートに解きたい問題を入力すると、
解答シートのAに解答が表示される仕組みです。
A~Dの各セルには数式が入っていて、
循環して参照された数式を反復計算させることで実装しています。

数式の内容

もう少し細かく見ていきましょう。
A~Dの役割と、入れてある数式の例をご紹介します。

・A
問題シートの入力内容と、Dで特定した数字を表示します。
最終的には解答が表示されます。

(例)
=IF('問題'!B2<>"",'問題'!B2,IF(LEN(V12)=1,V12+0,""))

・B1~B3
Aを行・列・ブロックで見て、既に埋められた数字があれば表示します。

(例)
B1:=IF(OR(B2=1,C2=1,D2=1,E2=1,F2=1,G2=1,H2=1,I2=1,J2=1),1,"")
B2:=IF(OR(B2=1,B3=1,B4=1,B5=1,B6=1,B7=1,B8=1,B9=1,B10=1),1,"")
B3:=IF(OR(B2=1,B3=1,B4=1,C2=1,C3=1,C4=1,D2=1,D3=1,D4=1),1,"")

・C
AとB1~B3から、各マスで入る可能性がある数字を調べます。
特定済のマスはその数字を表示しています。

(例)
=IF(B2+0>0,B2&"",IF(OR(L2<>"",$B$12<>"",L12<>""),"",1)
&IF(OR(M2<>"",$B$13<>"",M12<>""),"",2)
&IF(OR(N2<>"",$B$14<>"",N12<>""),"",3)
&IF(OR(O2<>"",$B$15<>"",L13<>""),"",4)
&IF(OR(P2<>"",$B$16<>"",M13<>""),"",5)
&IF(OR(Q2<>"",$B$17<>"",N13<>""),"",6)
&IF(OR(R2<>"",$B$18<>"",L14<>""),"",7)
&IF(OR(S2<>"",$B$19<>"",M14<>""),"",8)
&IF(OR(T2<>"",$B$20<>"",N14<>""),"",9))

・D
Cから、行・列・ブロックの中で他に候補が無く数字が特定できる状況かを調べます。
例えば、その行で「1」を入れられるのはこのマスしかない!というパターンを探します。

(例)
=IF(AND(COUNTIF(V2,"*1*")=1,OR(COUNTIF(V2:AD2,"*1*")=1,COUNTIF($V$2:$V$10,"*1*")=1,COUNTIF(V2:X4,"*1*")=1)),1,
IF(AND(COUNTIF(V2,"*2*")=1,OR(COUNTIF(V2:AD2,"*2*")=1,COUNTIF($V$2:$V$10,"*2*")=1,COUNTIF(V2:X4,"*2*")=1)),2,
IF(AND(COUNTIF(V2,"*3*")=1,OR(COUNTIF(V2:AD2,"*3*")=1,COUNTIF($V$2:$V$10,"*3*")=1,COUNTIF(V2:X4,"*3*")=1)),3,
IF(AND(COUNTIF(V2,"*4*")=1,OR(COUNTIF(V2:AD2,"*4*")=1,COUNTIF($V$2:$V$10,"*4*")=1,COUNTIF(V2:X4,"*4*")=1)),4, IF(AND(COUNTIF(V2,"*5*")=1,OR(COUNTIF(V2:AD2,"*5*")=1,COUNTIF($V$2:$V$10,"*5*")=1,COUNTIF(V2:X4,"*5*")=1)),5, IF(AND(COUNTIF(V2,"*6*")=1,OR(COUNTIF(V2:AD2,"*6*")=1,COUNTIF($V$2:$V$10,"*6*")=1,COUNTIF(V2:X4,"*6*")=1)),6,
IF(AND(COUNTIF(V2,"*7*")=1,OR(COUNTIF(V2:AD2,"*7*")=1,COUNTIF($V$2:$V$10,"*7*")=1,COUNTIF(V2:X4,"*7*")=1)),7, IF(AND(COUNTIF(V2,"*8*")=1,OR(COUNTIF(V2:AD2,"*8*")=1,COUNTIF($V$2:$V$10,"*8*")=1,COUNTIF(V2:X4,"*8*")=1)),8,
IF(AND(COUNTIF(V2,"*9*")=1,OR(COUNTIF(V2:AD2,"*9*")=1,COUNTIF($V$2:$V$10,"*9*")=1,COUNTIF(V2:X4,"*9*")=1)),9,)))))))))

※所々に入っている「&””」や「+0」は文字列や数値の変換で書いていますが、
 今思えば文字列で統一すればよかったです。反省点。

このA~Dを反復計算させて数字を特定していきます。
反復計算はデフォルトではオフ設定のため、オプションからオンに変更しておきます。
上で貼った画像のような難しい問題には力及ばずですが、
簡単な問題で試すとこんな感じです。

改良点

今回作成した数式は、
ルールを数式の形で書き表したものがほどんどです。
数独をやったことがある方なら分かるかと思いますが、
数独には細かいテクニックが数多くあります。
例えば二国同盟とかXウイングとかと呼ばれるものですね。
それらを一つ一つ実装していけば、
そのテクニックを使えば解ける問題は解けるようになります。
とは言え、ルールだけでも上記の数式をそれぞれ81マス分作成していて、
そこに細かいテクニックを1つずつ実装していくとなると、
あまりにも途方もない作業となります。
そのため、数式だけで実装できる現実的な完成度はこの辺りが限度として、
今回の思いつき研究は終了となります。
(アプローチの方向を変えて、数字を全パターン試すのもアリかもです。
 上手いやり方が思いついたら皆さんも作ってみてください。)

まとめ

偶にはこんな風に思いつきだけで行動してみるのも
良い気分転換になるのでオススメです。
思わぬ発見があったり、
不思議とモチベーションが上がったりするかもしれませんよ。