findIndexBy
順序付け関数によってリストで選択された要素のインデックスを返すを作成しようとしています。この関数は、リストをソートして先頭の要素を返すのと同じですが、サイズ制限なしでリストを処理できるように実装したいと考えています。
findIndexBy :: (Ord a) => (a -> a -> Bool) -> [a] -> Integer
findIndexBy f (x:xs) = findIndexBy' xs x 1 0
where
findIndexBy' [] _ _ i = i
findIndexBy' (x:xs) y xi yi = if f x y
then findIndexBy' xs x (xi + 1) xi
else findIndexBy' xs y (xi + 1) yi
この実装でStack space overflow
は、次の例のように大きなリストを処理するときに取得します (自明)。
findIndexBy (>) [1..1000000]
この問題を解決するためのより洗練された解決策があるはずであることはわかっています。また、最も慣用的で効率的な解決策を知りたいと思っていますが、関数の何が問題なのかを本当に理解したいと思っています。
私は間違っているかもしれませんが、私の実装はfindIndexBy'
端末再帰に基づいていると思うので、コンパイラが末尾呼び出しを最適化しないように見える理由がよくわかりません。
if/then/else が原因である可能性があり、次のことも試してみると、同じエラーが発生する可能性があると思いました。
findIndexBy :: (Ord a) => (a -> a -> Bool) -> [a] -> Integer
findIndexBy f (x:xs) = findIndexBy' xs x 1 0
where
findIndexBy' [] _ _ i = i
findIndexBy' (x:xs) y xi yi = findIndexBy' xs (if f x y then x else y) (xi + 1) (if f x y then xi else yi)
末尾呼び出しの最適化が実行されている (実行されていない) 場所を示すようにコンパイラに依頼する簡単な方法はありますか?
参考までに、以下は私が Clojure で書いた同等の関数で、現在 Haskell に移植しようとしています。
(defn index-of [keep-func, coll]
(loop [i 0
a (first coll)
l (rest coll)
keep-i i]
(if (empty? l)
keep-i
(let [keep (keep-func a (first l))]
(recur
(inc i) (if keep a (first l)) (rest l) (if keep keep-i (inc i)))))))
参考までに、前述の Haskell コードは-O3
フラグを使用してコンパイルされています。
[leventovの回答後に編集]
問題は遅延評価に関連しているようです。$!
とについてはわかりましseq
たが、それらを使用して元のコードを修正する場合のベスト プラクティスは何だろうと思います。
の関数に依存する、より慣用的な実装にまだ興味がありますData.List
。
[編集]
最も簡単な修正は、ステートメントyi `seq`
の前に最初のスニペットを追加することです。if