【发布时间】:2013-09-23 05:05:54
【问题描述】:
我正在尝试编写一个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 的函数的更多惯用实现感兴趣。
[编辑]
最简单的解决方法是在if 语句之前的第一个sn-p 中添加yi `seq`。
【问题讨论】:
-
您不会发现任何概念上的不同。
findIndexBy f (x:xs) = snd $ fst $ foldl' (\(i, found@(foundI, foundX)) x -> (i + 1, if f x foundX (i + 1, x) else found)) xs (1, (0, x))
标签: haskell optimization lazy-evaluation tail-recursion