いもあらい。

プログラミングや哲学などについてのメモ。

数独 with バックトラック法。

2006-04-29 14:01:00 |  Prog...
数独をバックトラック法で解くプログラムを作ってみた。

数独。のエントリを書いて、ところでこのバックトラック法ってどんなんだろうとググッてみたら、すんごい単純なアルゴリズムなんですね(^^;
まぁ、再起呼び出しする必要があるところがちょこっと難しいぐらいで。

ということで、早速Perlを使って実装してみました。(CでもJavaでもないw ちなみに、Javaはやればやるほど魅力がなくなったので、止めました。どうやったらあんな中途半端で汚くて使えなくて重たくて(※実行し始めるまでが遅すぎ)どうしようもない言語が設計できるんだか・・・ Ruby(これはホントに美しいですね)を見習えとは言わないから、せめてPerlぐらいは見習って欲しいものです)

で、実行してみたのですが、これが思いのほか早くてビックリ。
ほとんど総当りみたいなもんだろうと思っていたので、入力してから平均1分、悪ければ5分以上かかるかなぁ、と思っていたのですが、ためしに解かした3題は、どれも1秒かからないぐらいで解いてしまった・・・
思ったよりも試行錯誤の数が少なくて済んでしまうのかもしれないです。(解析するのはめんどいけど・・・)

暇があれば練習もかねてTkインタフェースのGUI版も作ってみたいですが、とりあえずはCUI版のソースは以下。

==> SudokuMatrix.pm <==
package SudokuMatrix;

#--------------------------------------------------
# SudokuMatrix 数独の表クラス
#
# =フィールド=
# なし
# 代わりに、オブジェクト自体が9*9の2次元配列
#
# =メソッド=
# $obj new(void); オブジェクトを作成
# $obj オブジェクト
#
# $obj init(void); オブジェクトを初期化
# $obj オブジェクト
#
# void set($i, $j, $num); 数字をセット
# $i 行(1~9)
# $j 列(1~9)
# $num 数字(1~9)
#
# $num get($i, $j); 数字を取得
# $num 数字(1~9)ただし、未定義の場合は0
# $i 行(1~9)
# $j 列(1~9)
#
# void set_matrix($matrix); 2次元配列で数字をセット
# $matrix 9*9の2次元配列のリファレンス
#
# $bool check($i, $j); i行j列の周りで重なりがないかチェック
# $bool 重なりがなければ1、あれば0
# $i 行(1~9)
# $j 列(1~9)
#--------------------------------------------------

sub new{

    my $class = shift @_;


    my $obj = bless [], $class;
$obj->init();


}

sub init{

    my $self = shift @_;


    foreach my $i (1..9){
foreach my $j (1..9){
$self->[$i-1][$j-1] = 0;
}
}


    return $self;


}

sub set{

    my ($self, $i, $j, $num) = @_;
$self->[$i-1][$j-1] = $num;


}

sub get{

    my ($self, $i, $j) = @_;
return $self->[$i-1][$j-1];


}

sub set_matrix{

    my ($self, $matrix) = @_;


    foreach my $i (1..9){
foreach my $j (1..9){
$self->[$i-1][$j-1] = $matrix->[$i-1][$j-1];
}
}


}

sub check{

    my ($self, $i, $j) = @_;


    --$i; --$j;
my $num = $self->[$i][$j];


    #横のライン
foreach(0..8){
if(($_ != $j) and ($num == $self->[$i][$_])){
return 0;
}
}


    #縦のライン
foreach(0..8){
if(($_ != $i) and ($num == $self->[$_][$j])){
return 0;
}
}


    #箱の中
my $rel_x = $i % 3; #相対座標
my $rel_y = $j % 3;
my $base_x = $i - $rel_x; #オフセット
my $base_y = $j - $rel_y;
foreach(1..2){ #右斜め下へ走査
++$rel_x; $rel_x %= 3;
++$rel_y; $rel_y %= 3;
if($num == $self->[$base_x + $rel_x][$base_y + $rel_y]){
return 0;
}
}
++$rel_x; $rel_x %= 3;
++$rel_y; $rel_y %= 3;
foreach(1..2){ #右斜め上へ走査
++$rel_x; $rel_x %= 3;
$rel_y += 2; $rel_y %= 3;
if($num == $self->[$base_x + $rel_x][$base_y + $rel_y]){
return 0;
}
}


    return 1;


}

1;

==> Backtrack.pm <==
package Backtrack;

#--------------------------------------------------
# Backtrack バックトラック法による解法アルゴリズム
#
# =フィールド=
# matrix SudokuMatrixクラスのオブジェクト
# katei 仮定しているセルの配列
# nokori 残っているセルの配列
#
# =メソッド=
# $obj new($matrix); 新しいオブジェクトを作成
# $obj オブジェクト
# $matrix SudokuMatrixクラスのオブジェクト
#
# $obj init($matrix); オブジェクトの初期化
# $obj オブジェクト
# $matrix SudokuMatrixクラスのオブジェクト
#
# $bool backtrack(void); バックトラック法を用いる
# $bool 成功すれば1、失敗すれば0を返す
#--------------------------------------------------

use SudokuMatrix;

sub new{

    my ($class, $matrix) = @_;


    my $obj = bless {}, $class;
$obj->init($matrix);


}

sub init{

    my ($self, $matrix) = @_;


    $self->{matrix} = $matrix;
$self->{katei} = [];
$self->{nokori} = [];


    foreach my $i (1..9){
foreach my $j (1..9){
if($matrix->get($i, $j) == 0){
push @{$self->{nokori}}, [$i, $j];
}
}
}


    return $self;


}

sub backtrack{

    my $self = shift @_;


    my $matrix = $self->{matrix};
my $katei = $self->{katei};
my $nokori = $self->{nokori};


    #ターゲットの取得
#残りがなければ1を出力して終了
my $target;
if(@$nokori){
$target = shift @$nokori;
}else{
return 1;
}


    #ターゲットの数字を1つ上げる
my $num = $matrix->get(@$target);
$num = ($num + 1) % 10;
$matrix->set(@$target, $num);


    #全部試した場合は一つ前に戻る
#前にもどれなければ0を出力して終了
if($num == 0){
unshift @$nokori, $target;
if(@$katei){
unshift @$nokori, pop @$katei;
}else{
return 0;
}
return $self->backtrack();
}


    #チェックをして、大丈夫なら次のセルへ
#大丈夫でないなら次の数字へ
if($matrix->check(@$target)){
push @$katei, $target;
return $self->backtrack();
}else{
unshift @$nokori, $target;
return $self->backtrack();
}


}

1;

==> CUI.pm <==
package CUI;

#--------------------------------------------------
# CUI CUI用のインターフェース
#
# =フィールド=
# silent オプションが指定さえていれば1
# help オプションが指定されていれば1
#
# =メソッド=
# $obj setup(void); 引数を解析してオブジェクトを作成
# $obj 新しいオブジェクト
#
# $matrix make_matrix(void); SudokuMatrixを作成
# $matrix SudokuMatrixクラスのオブジェクト
#
# void print_ans($matrix); 答えを出力
# $matrix SudokuMatrixクラスのオブジェクト
#
# void print_help(void); ヘルプを出力
#--------------------------------------------------

use SudokuMatrix;

$HELP = <<END_OF_HELP;
sudoku 数独をバックトラック法で解くプログラム

数字は標準入力から9×9の行列で入力
各数字は1~9で、空欄には0を入力

=オプション=

 -s, --silent    冗長出力しない
-h, --help ヘルプを出力して終了


END_OF_HELP

sub setup{

    my $class = shift @_;


    my $obj = bless {
silent => 0,
help => 0,
}, $class;


    foreach(@ARGV){
if(/(-s|--silent)/){
$obj->{silent} = 1;
}
if(/(-h|--help)/){
$obj->{help} = 1;
}
}


    return $obj;


}

sub make_matrix{

    my $self = shift @_;


    unless($self->{silent}){
print "数字を入力してください。\n";
}


    my $matrix;
foreach(1..9){
my $str = <STDIN>;
chomp($str);
my @num = split /\s*/, $str;
push @$matrix, \@num;
}


    my $obj = SudokuMatrix->new();
$obj->set_matrix($matrix);


    return $obj;


}

sub print_ans{

    my ($self, $matrix) = @_;


    unless($self->{silent}){
print "\n答え\n";
}


    foreach my $i (1..9){
foreach my $j (1..9){
my $num = $matrix->get($i, $j);
print " $num";
}
print "\n";
}


}

sub print_help{

    print $CUI::HELP;


}

1;

==> sudoku <==
#!/usr/bin/perl

#--------------------------------------------------
# sudoku 数独をバックトラック法で解く(CUI)
#--------------------------------------------------

use SudokuMatrix;
use Backtrack;
use CUI;

my $prog = CUI->setup();

if($prog->{help}){

    $prog->print_help();
exit;


}

my $matrix = $prog->make_matrix();
my $algo = Backtrack->new($matrix);

my $result = $algo->backtrack();

if($result){

    $prog->print_ans($matrix);


}
else{

    "答えが出ませんでした。\n";


}


数独。

2006-04-28 23:15:00 |  Etc...
数独に関して。

R25でなにやら数独が取り上げられていて、以前どこで見たのかは忘れたけれど、やってみるとやっぱり難しい。
理屈はある程度分かっているけれど、実際にそれを適応できるマスを発見できないというのがなかなかくやしい。

それはさておき。
解いてて気になるのは、「解が一意に定まっているのか」ということ。
ネットでざっと見た限り、解が一意に定まらない問題もありうるみたい。
「解が一意に定まる条件」とか、あるいは「解が得られる条件」というものが気になった。

で、これだけ大量に問題も作られているのだろうし、問題作成のアルゴリズムとか、解くためのアルゴリズムが確立されているのかと思いきや、Googleで調べた限りだとあたりというものが見当たらなかった・・・
やはりなかなか難しい問題なのかな?

解き方のアルゴリズムでちょっと出てたのは、バックトラック法?とか、矛盾を導き出す方法?とか、(相互結合型)ニューラルネットワークによるもの。
たしかにクロスバスイッチ問題のお化けみたいなものと考えれば、ニューラルネットワークによる解放もそれなりに有効なのかな・・・?(正直、別の問題で実際に実装してみた感触としては、ニューラルネットワークはあまり優秀だと思えないのだけれど・・・)

ちょっと考えてみたいと思います。


音が出ない・・・。

2006-04-24 01:33:00 |  Gentoo...
現在ALSAと格闘中。

やっぱり音がでないとさびしいということで、音を出したいわけですが、さっぱり音が出てくれない。
Gentoo Linux ALSAガイドの通りにやっているはずなのになぁ。

なんか音を出すためだけに数十時間使っていて(誇張でもなく、実際に。今日だけでも6~7時間はいじったり調べたりしてるし。)、しかもそれでなお出せていないから、さすがに嫌になってくる。

どうやら、使っているパソコンのサウンドカード(と言ってしまって良いのかはよく分からないけれど)は、オンボードのもので、
# lspci |grep audio
00:1f.5 Multimedia audio controller: Intel Corporation 828011EB/ER (ICH5/ICH5R) AC'97 Audio Controller (rev 02)
っていう感じなんですね。
同じものを使っている人がやっぱりいて、そのひとたちは普通にsnd_intel8x0というモジュールをを使ってうまくいっているみたいなんですが・・・

カーネルも何度か再構築したりしたんですよ。
Gentoo-sourcesの最新のもの(2.6.16-r3)にして、必要なモジュールも入れたはず。
# lsmod

 Modules           Size   Used by
snd_intel8x0 29252 -
snd_ac97_codec 91208 -
snd_ac97_bus 1640 -
wacom 14920 -


と、ちゃんと読み込まれてるしなぁ・・・

alsaconfがちゃんと動かなかったりしたんですが、これはモジュールとしてでなく組み込みでカーネルをコンパイルしちゃったからで、モジュールとしてやればちゃんと動く感じ。
ここもでちゃんと検出されていて、(ただし、他のlegacyっていうのも検出されている・・・?)うまくいっているはず、、、

alsamixerをするとなぜかMasterのところは音量があげられないんだけれど、MasterMonoというところは音量があげられて、他もあげられるところはあげたんだけれど、反応なし、、、
ここらへんをどう対処したらいいものかもよく分からない・・・

/var/log/kern.logとかを見てみると、(いまだにlogの見方がよく分からない^^;)4月23日(つまり今日)の17:55の起動では
ALSA device list:

 #0: Intel ICH5 with CM19739 at 0xf4181000. irp 16


てなっているかと思えば、4月23日(だから今日)の18:04以降の起動では
ALSA device list:

 No soundcards found.


とかってなっるし。
う~ん、、、いずれにしろ音は出ていないんだけど(^^;

気になったのでもう一度再起動、、、やっぱり

 No soundcars found.


になってる。
(けど、ALSAはちゃんと動いた?起動スクリプト?の出力では、たしかに[OK]ってな表示が・・・?

まったく意味不明なんだけれど、考えられる原因として、スピーカーがUSBスピーカーだということ。
サウンドの仕組みがよくわかっていないんだけど、そこにちゃんと出力が回されていないとかっていうのはないのかなぁ・・・
ただ、普通のスピーカー端子にヘッドホンをつないで試してもやっぱり音が出ないから、それが原因だとは考えづらいというのも・・・

誰かアドバイスを、、、


愛人[AI-REN]。

2006-04-22 01:07:00 |  Review...
愛人[AI-REN]を読んで。

以前、心に少年少女の輝きをレビューで気になって、今日やっと5巻を入手して読んだのですが・・・

いや、素晴らしい!
ここまで活き活きとした生が、そして死が描かれているとは!

死への恐怖、それは同時に生への渇望でもある。
死ぬことを恐がって何も出来なくなってしまっては元も子もないですが・・・しかし、安易な「救い」に手を伸ばしてしまった瞬間に、その人の「生」は終わりを迎えてしまう──苦しみもがいていた「その人」はいなくなってしまう。

強く印象に残っているシーンから。

男 「なんて言いわけすればいい・・・
   明日もこの夜の中で
   新しい生命は何も知らされずに
   生まれてくるだろう・・・
   なんて言いわけすればいい
   生まれる前から
   きみは呪われていると
   世界は闇だと言わなければならない
   ゆるして・・・」
ナギ「ゆるさない!
   ゆるされないぞ!
   生きてなさい!
   まだだめです!
   朝がくるまで生きてなさい!
   呪われたまま!
   許されないまま!
   生きてなさい!
   呪われ病んで
   闇の中に生まれてくる子供たちを
   ようこそと手を広げて迎えましょう!
   わたしたちが歓迎しなければ
   どうするのです!」
(#38 夜 より)



そして、最後。
多くの人々の祝福のもとに生まれる新しい生命。

寒い・・・
まぶしい・・・
こわい・・・
こわいよ・・・
大丈夫 こわくないよ
この世界は 君を
応援しているよ
待ち望んでいるよ
歓迎しているよ
よく聞いてごらん
君の扉をたたく手は 何て言ってる?
みんなは君に 何て言ってる?
勇気を出して 目をあけてごらん
君を迎える たくさんの人たちと
うつくしい世界が 必ず見えるから
これから先
自分のことが 小さく醜く
汚いものにしか感じられない時も
くるでしょう
世界のすべてが よそよそしくて
冷たい石のようにしか感じられない時も
くるでしょう
苦しくて 苦しくて
泣き声さえ出せない時も くるでしょう
でも
おそれないで
それはとてもまっとうなことです
こわがる必要はありません
忘れないで
いま この瞬間
世界のすべては 全身全霊で
君を祝福している!
忘れないで
君はかけがえなく
愛されて生まれてきたんだ!
(#43 春の日に、君と帰る より)



祝福された生・・・
素敵すぎる・・・



それにしても、今改めてkagamiさんのレビューを見ると・・・
てっきり生への意志への称賛が書かれているだと思っていたら、そうではないんですよねぇ
むしろ、教会的な救いに価値を見出しているような、ニーチェの言っていることとはまるで正反対のことを書いてますしねぇ。
(ニーチェの引用(怪物うんぬん)も、まるっきり意味が違うような・・・あれは限りない懐疑、否定が可能なのかという自己への戒め、獅子の姿への批判ですよね?)
イクルも全然聖人君子なんかじゃなくて、ただの一個の人間──最後の最後まで生き抜いた一人の「人間」にすぎません。
もちろん、それが出来るかどうかは別の話になりますが(そして、自分には難しいとも思いますが)、どういった生が活き活きとした生なのか、人の心を動かすのかということが描かれていることは、十分に意味を持つと思います。


間違い探し(解答編)。

2006-04-19 01:53:00 |  Etc...
以前書いたエントリ間違い探し。の解答編。

以前出した問題は、次のようなものです。

(問)この問題には間違いが2つあります。何でしょう?



どうでしょう?

答えを言ってしまえば、
「“間違いが2つある”というのが“間違いが1つある”の間違いで、その1つとは『“間違いが2つある”というのが“間違いが1つある”』という間違いである」
というのが正確な解答です。
少し曖昧になるかもしれませんが、
「『“間違いが2つある”というのが“間違いが1つある”の間違いである』という間違いが1つある」
というのでも大丈夫です。

あやしそうに見えますが、間違いの文と正しい文とを並べて書いてあげればこれが正しいことが分かります。

(間違いの文)
この問題には間違いが2つあります。何でしょう?

(正しい文)
この問題には間違いが1つあります。何でしょう?

正しい文から見れば、間違いの文には正しい文の言及どおり1つだけ間違い(=正しい文とは異なる場所、すなわち太字のところ)が存在します。
すなわち、正しい文は確かに正しいのです。

しかも、この正しい文というのは一見任意に与えたように見えますが(=他にも正しい文の与え方がありそうですが)、唯一つ、これのみに定まることが分かります。

次のように考えてみましょう。

そもそも「間違い」や「間違いの個数」とは何なのか、と考えると、「正しい」とされるものが存在した上で「正しい」とされるものと異なる点が「間違い」であり、その個数が「間違いの個数」となります。

ここで、「間違いが2つある」というのが正しいと仮定しましょう。
すると、間違いの文には正しい文からの変更点が2つ存在するというのは正しいことになります。
しかし、変更を与えるとすれば「この問題には」という部分か、「何でしょうか?」の部分になります。
「何でしょうか?」は「何と何でしょうか?」の間違いであるとしても、問題となるのは「この問題には」の方で、こちらは変更が不可能です。
というのも、ここで主語が変わってしまうと、「正しい文」の間違いの文であったはずの問題の文が、「正しい文」の間違いの文でなくなってしまうからです。
実際に変えてみれば分かりますが、例えば
「その問題には間違いが2つあります。何と何ですか?」
が正しい文として、「これで確かに間違いが2つだ」と答えたとすると、しかしここでまさに「確かに間違いが2つ」と言うことで“この問題に間違いが2つある”というのがこの問題の正しい文であると認めてしまっているので、「この問題は」というのは間違いにはなりえません。
よって、高々1つの間違いしか「間違いが2つある」以外の部分には見つけられないので、「間違いが2つある」というのと矛盾します。
すなわち、「間違いが2つある」というのは間違いです。

さて、ここで、正しい文が「間違いがN個ある」(N:fixed)としましょう。Nは0の場合も含みます(=間違いがない、というのが正しい、ということ)。

N=0の場合から考えていきましょう。
よく本に載っている解答はこれが多いのですが、しかしこれはパラドックスを引き起こします。
「“間違いが2つある”というのが“間違いがない”の間違い」
と指摘したとします。
しかし、このとき正しい文から間違った文というのを顧みると、“間違いがない”というのが正しいはずなのに、間違いがあったわけですから、正しい文は正しくありません。

N=2の場合について考えると、「間違いが2つある」というのが正しい文になるわけですから、元の文の「間違いが2つある」というのは間違いではなくなってしまいます。
これはさっきの議論の結論と矛盾します。

N>=3の場合を考えて見ましょう。
まず、“間違いが2つある”が“間違いがN個ある”の間違いである、というのが1つあるとして、それ以外にN-1(>=2)個の間違いがあることになります。
しかし、先の議論の通りこの部分以外に2つ(以上)の間違いを作ることは出来ないわけですから、正しい文は正しくなりません。

したがって、N=1のときにのみ正しい文は確かに正しい文になっています。

すなわち、最初に答えた
「“間違いが2つある”というのが“間違いが1つある”の間違いで、その1つとは『“間違いが2つある”というのが“間違いが1つある”』という間違いである」
というもののみ、矛盾が生じません。
つまり、これ以外に正しい文の与え方はないことになります。



これに似ていますが、パッと浮かんだのが次のもので、「この命題は真偽が定まる」という命題は真か偽か、という問題。

「この命題は偽である」という命題は真か偽か、というのはパラドックスを起こすことで有名ですが(自分のエントリ、自己言及の難しさ。でも取り上げています)、実は上の命題であれば真偽が定まります。(これと同じようなものは今まで見たことがありません。オリジナル?)

「この命題は真偽が定まる」という命題について、この命題の真偽が定まるのであれば、その言及は正しいわけですから、真偽は実際に真と定まります。
たいして、真偽が定まらないのであれば、それはこの命題の真偽が偽であると定まることになるわけですが、真偽が定まらないというのと矛盾します。
よって、真偽は定まるしかなく、すなわちこの命題は真となります。

同様に、「この命題は真偽が定まらない」という命題は真か偽か、と考えると、真偽が定まるのであれば、この言及は偽となり、真偽は実際に偽と定まっています。
そして、真偽が定まらないのであれば、この言及は真であると定まることになりますが、それは真偽が定まらないということに矛盾します。
よって、真偽は定まるしかなく、真偽は偽となります。



一見パラドックスになってしまいそうだけれどパラドックスになっていなかったり、無限に続くメタ言及を避けることが出来ている(ように見える)ので、もうちょい考察を加えられたらな、と思います。
(なんとなく、あやしいんですよねぇ(^^;