Category Archives: PROLOG

Problem 6 2乗和の差

問題

最初のほうのはみんな簡単ですね…歯ごたえあるやつまでとばしてやるかな…

プログラム:

実行結果:
1 ?- solve.
***********************(解答伏せます)**********************
true.

Problem 5 最小公倍数

問題

1~20の最小公倍数を求めているだけ

プログラム:

実行結果:

Problem 44 五角数

問題

assertのハッシュとselectの順番に頼った力まかせのひどいプログラム。エレガントさのかけらもない。遅いし…

make_pent_list(10000,PentLst)の数字も小さいものから試していって見つかるまでだんだんと増やしてった(ひどい)

答えは合ってた。


:-use_module(library(clpfd)).

main:-
make_pent_list(10000,PentLst),
select(B,PentLst,Rest),
select(A,Rest,_),
A < B,
Sum is A+B,
p(Sum),
Diff is B-A,
p(Diff),
write([Diff]).

make_pent_list(0,[]):-!.
make_pent_list(Idx,[First|Rest]):-
!,
First is Idx * (3*Idx - 1) / 2,
assert(p(First)),
Idx1 is Idx - 1,
make_pent_list(Idx1,Rest).

% 実行結果
%
%1 ?- time(once(main)).
%[5482660]
%% 252,016,073 inferences, 295.203 CPU in 296.719 seconds (99% CPU, 853704 Lips)
%true.

Problem 4 最大回文積

問題

問題を「3桁の数3つの最大回文積を求めよ」と読み間違っていてずっと解答が合わずに頭を抱えていた。勘違いしていた問題のほうが本題より難しいがこちらの答えは以下のとおり967262769になるようだ。
1 ?- time(solve).
967262769=999*989*979
% 30,373,524 inferences, 16.703 CPU in 16.734 seconds (100% CPU, 1818434 Lips)
true
between述語のデクリメントバージョンdetween_decを作成して対応した。

プログラム:

実行結果:

Problem 35 循環素数

問題

解説:
①エラトステネスのふるいによる素数列挙、すべてsosu述語としてassert
②rotate述語で転回した数字をsosu述語で存在するか確認
 すべての転回に関して存在すればretract
 見つかった分カウントをアップ

結構速く動作してると思います(自分の基準では)

実行結果:

Problem 3 最大の素因数

問題

実行結果:

Problem 11 格子内の最大の積

問題

最初の問題は簡単だなあ…作業感出てきた…早くもう少し難しくなってほしい

実行結果(解答伏せています):

Problem 10 二百万以下の素数の和

問題

プロジェクトオイラーは一度素数列挙ロジックを作るとあちこちで使いまわせる。

実行結果:

Problem 1 3と5の倍数

問題

CLPFDライブラリ読み込んでるけど使ってなかった…

実行結果
[1] 2 ?- adding(999,R).
***********************(解答伏せます)**********************

[Prolog]制約論理プログラミングへの条件追加と速度の向上に関して

2週間ほど前にProject Eulerの存在を知り、面白いのでチャレンジしている。本日の時点では26問解いた。全部Prologで解いている。

Project Euler

ところどころCLPFDを使える問題があり、使っている。

制約論理プログラミングの動作の特徴?みたいなのを感じられる面白い問題があったので、紹介しようと思う。

Probrem 9 特別なピタゴラス数

この問題は、CLPFDだと探索の処理を一切書かずに以下のようにただ定義だけをずらずら書くだけで解ける。

問題の条件をそのまま書いただけのようなプログラムだけど、本当にこれだけで解けます。

ただ、これだと非常に処理時間がかかかる。僕のしょぼいマシンでは、
以下のように150秒かかった。

ネットで調べるとこのピタゴラス数の法則のようなものがいくつかヒットするのですが、そのひとつが以下の図のようなもので、

triangle

ようするにBとCの関係に関してなのですが、長さBの正方形の辺を1つづつ大きくして一回りづつ囲むように配置してゆくといつか長さCの正方形と等しくなるというもので、しかもこの大きくした分の面積はA × Aと等しいというもの。

大きくした分の面積は、N(=1,2,3..)の数列として以下のように表すことができる。
En = 2 × N × B + N × N
そして、A × A = En

前述のプログラムにこの条件を追加して動作させると、答えを出す速度が段違いに速くなる。

実行結果:

10倍近く速くなった。
多分Aの探索空間が新しい条件の数列で絞り込まれて速くなるのだと思う。

「思う」と書いたのも制約論理プログラミングの特徴といえば特徴で、条件を書くたびに大量の制約伝播用のプログラムが内部で自動的に生成されるため、厳密な動作が非常に追いずらい。ステップ実行などのデバックもしずらい(CLPFDライブラリ内部で自動的に大量のバックトラックが発生している)。

これは悪い面で、動作が見積もれないことが原因となり正確・確実な動作を期待される産業分野でなかなか採用されないかもしれない。ちゃんとした企業ほど異常動作のときなどの原因追及フェーズを徹底的に行っているので、そのときに「思う」とか「これ入れたら速くなるかも」とかは通用しないわけです。動作確認のためのログを埋め込むのも結構大変です(ライブラリに直接埋め込まないと駄目だし制約伝播状態を出力した解析困難なログになることが予想される)

ちなみに制約伝播というのは、prolog の attributed_variableの仕組みを使って変数の探索空間が変更された時点でキックされる述語が予約されていて、ドミノ倒しのように次々と他の変数の探索空間をせばめてゆくという手法です。

CLPFDのソースを読んでゆくとわかるのですが A in 1..500 などという探索空間は木構造で表現されており、探索空間の変更はこの木構造を変えることで行っている。

たとえば A の木構造が最初 左の枝が1、右の枝が500 の状態で、この状態から100~200の可能性がないことが判明した時点で、右の500の枝の部分に100..200のノードが新しく追加される感じ?

Prologの変数は基本的に再代入不可なのですが、attributed_variableは再代入可能かつ履歴情報を保持していてバックトラック時に直前の値に戻る(Prologの通常の自由変数はバックトラック時は未設定状態になるだけで履歴情報は持っていない)ようなのでこれを利用しているのでしょう。

CLPFDは非常に簡単に記述できて問題が解けるため、勉強し始めのころはまるで魔法のツールのようだと感じたが、上記のような、問題自身の性格を表す条件を入れないとすぐに処理時間が膨大になってしまうように感じる。

当たり前のことだが、問題に対する洞察・分析も非常に大切ということだろう。