基礎から始める覆面算
Scala の for 式で覆面算を解いているエントリを見つけた人が Haskell と Groovy で解きながらリスト内包表記について考えていた。
このようなエントリを見かけると、いつも面白いなーと感心するけど、どうも私は実戦で使いこなせていない。おそらくは、パフォーマンスと柔軟性に欠けるからだと思う。例えば、覆面算の問題パターンを変えると List comprehension を使えないんじゃないとか、パフォーマンスはバックトラックに比べてどうなるのとか。
もうちょっと詳しく考えるため、実際にコードを書いてみた。
まずは、元ネタと同じように素直に総当りをしてみる。
確かに scala の for式 は便利だなと感じる反面、実行時間は5秒も掛かっている。遅い、遅すぎる。
そこで、for式の範囲内で枝刈りをしてみることにした。
枝刈りのアイディアは、下位から順番に数字を当てはめていき、筆算が成立しなくなった時点で其の当てはめを打ち切るもの。
実行時間は20ms未満となった。なるほど、この程度の問題であれば、for式の範疇でも上手く枝刈りが出来るのか。
次は覆面算の問題を引数で与えられるように一般化してみる。枝刈りの方針については、今までと全く同じだが、任意の数の筆算を許可しようと思うと、構造が全く変わってしまった。
もはや、for式は使えない。実際、私は for式を諦めて再帰で書いた。このあたりをスマートに書きたいのだが、うまい手はないものだろうか?
ベンチマーク結果一覧。各メソッドとも5回実行、毎回の所要時間を System.nanotime()で計測。
trial | 1 | 2 | 3 | 4 | 5 |
method #1 | 6744723400 | 6797389219 | 6787831489 | 6621700643 | 6632244455 |
method #2 | 18236895 | 18029371 | 17458337 | 17302694 | 17080834 |
method #3 | 120678891 | 119130650 | 118630953 | 119162734 | 118002919 |
method #4 | 79308821 | 79116997 | 79093104 | 80181925 | 78304648 |
method #5 | 40626669 | 40049150 | 40500380 | 41073120 | 40473074 |
完全に自分メモエントリ OTL