考え方
一番シンプルにHaskellらしい書き方
digits1000 :: [Int]
digits1000 = [7,3,1,6,7..]
solve :: [Int] -> Int
solve = maximum $ map (product . take 13) $ tails digits1000
tailsで1つずつ先頭を除いたリストを作っていき、そのリストごとに最初の13個の積を求めて最大値を探すだけ
遅延評価のおかげか、リストがたくさん出来ても先頭13桁しか評価されないのでそこまで重い処理ではなさそう
しかも、簡潔でHaskellらしい書き方になっている
積を求めない方法
上記のやり方は、書き方としては美しい。しかし、誰がどう見ても積を計算して比較するのは無駄
少なくとも命令型の言語でならこんなに無駄な計算をせずにアルゴリズムを工夫するはず!
と、言うわけで考えてみる
出て行く数字、入ってくる数字
今現在、1000桁の数字の中のとある13桁の数字を最大値として持っているとする。
1,2,3,4, [3,5,6,8,2,4,5,3,7,4,5,1,9] x,2,7,8
次の数字xが今持っている13桁の先頭3よりも大きければ、間違いなく最大値になるはず
ghci> product [3,5,6,7,2,4,5,3,7,4,5,1,9]
95256000
ghci> product [5,6,7,2,4,5,3,7,4,5,1,9,4]
127008000
こういう考えで、digits1000を先頭から走査して、1つ進む毎に出てく値と入ってくる値を比較するという方法をとれれば積の計算はいらなさそう
問題点
このままではいくつかの障壁にぶち当たる
問題点1
常に最大を更新し続けるのなら簡単だが、出て行く値の方が大きくなった場合にどうするか?
例えば
1,2,3,4, [3,5,6,8,2,4,5,3,7,4,5,1,9] 4,2,7,8 = 95256000
-> 1,2,3,4,3, [5,6,8,2,4,5,3,7,4,5,1,9,4] ,2,7,8 大きくなった = 127008000 ※最大
-> 1,2,3,4,3,5, [6,8,2,4,5,3,7,4,5,1,9,4,2] ,7,8 小さくなった = 58060800
-> 1,2,3,4,3,5,6, [8,2,4,5,3,7,4,5,1,9,4,2,7] ,8 大きくなった = 67737600
最大値になるたびに保存したいが、大きくなるたびに最大値になるわけではないので結局積を比較しないといけない
問題点2
0をどうするか?
積を求める方法では、簡単に0が入っていることが分かるが今の方法ではどうするか
問題点3
13桁のリストを保存するのはもったいない?
解決策
問題点3
先頭のインデックスだけ保存しとけば良さそう
問題点2
0が入ってきたらカウントを"+"、出て行ったら"-"をしてカウントが0のときは13桁の中に0は含まれていないと出来る
問題点1
Rational (分数)を使おう!
Haskellには分数を簡単に扱う型Rationalがある
1,2,3,4, [3,5,6,8,2,4,5,3,7,4,5,1,9] 4,2,7,8 = 95256000
-> 1,2,3,4,3, [5,6,8,2,4,5,3,7,4,5,1,9,4] ,2,7,8 大きくなった = 127008000 = 95256000 * (4/3)
-> 1,2,3,4,3,5, [6,8,2,4,5,3,7,4,5,1,9,4,2] ,7,8 小さくなった = 58060800 = 12700800 * (2/5)
-> 1,2,3,4,3,5,6, [8,2,4,5,3,7,4,5,1,9,4,2,7] ,8 大きくなった = 67737600 = 58060800 * (7/6)
このように、積は"入ってくる値 / 出て行く値"倍になる
これを利用して分数が1より大きくなったら最大値が更新されたとし、分数を1に戻すという操作をすれば
1STEPの計算は簡単な分数のかけ算のみにできる
例
1,2,3,4, [3,5,6,8,2,4,5,3,7,4,5,1,9] 4,2,7,8 = 95256000 (Rational = 1)
-> 1,2,3,4,3, [5,6,8,2,4,5,3,7,4,5,1,9,4] ,2,7,8 大きくなった = 127008000: (Rational = 4/3) -> ここでRational1に戻す
-> 1,2,3,4,3,5, [6,8,2,4,5,3,7,4,5,1,9,4,2] ,7,8 小さくなった = 58060800: (Rational = 1 * 2/5)
-> 1,2,3,4,3,5,6, [8,2,4,5,3,7,4,5,1,9,4,2,7] ,8 大きくなった = 67737600: (Rational = 2/5 * 7/6 = 14/15) !!! 大きくなったが最大でない