有几次我想遍历一个列表并挑选出具有某些属性的元素,这些属性也依赖于列表中的下一个元素.举一个简单的例子,我有一些代码可以计算函数f在指定的区间[a,b]上改变符号的次数.这在
for(double x=a; x<=b; x+=(b-a)/n){ s*f(x)>0 ? : printf("%e %e\n",x, f(x)), s=sgn(f(x)); }
在Haskell中,我的第一直觉是用它的尾部压缩列表,然后应用过滤器并用fst或其他任何东西提取元素.但这似乎笨拙而且效率低下,所以我把它变成了折叠:
signChanges f a b n = tail $ foldl (\(x:xs) y -> if (f x*f y)<0 then y:x:xs else x:xs) [a] [a,a+(b-a)/n..b]
无论哪种方式,我觉得有一种“正确”的方式来做到这一点(因为在Haskell经常出现)并且我不知道(或者只是没有意识到)它是什么.任何有关如何以更惯用或更优雅的方式表达这一点的帮助将不胜感激,一般来说,建议如何找到“正确”的做事方式.
这是一个使用paramorphism的“版本”(与问题不完全相同 – 但它应该足以说明一个paramorphism),首先我们需要para,因为它不在标准库中:-- paramorphism (generalizes fold) para :: (a -> ([a], b) -> b) -> b -> [a] -> b para phi b = step where step [] = b step (x:xs) = phi x (xs, step xs)
使用paramorphism就像使用折叠一样,但是看到累加器我们可以看到其余的输入:
countSignChanges :: [Int] -> Int countSignChanges = para phi 0 where phi x ((y:_),st) = if signum x /= signum y then st+1 else st phi x ([], st) = st demo = countSignChanges [1,2,-3,4,-5,-6]
与拉链相比,para的优点在于我们可以根据需要偷看其余的输入.