如何在功能上表达依赖列表中相邻元素的过滤器

有几次我想遍历一个列表并挑选出具有某些属性的元素,这些属性也依赖于列表中的下一个元素.举一个简单的例子,我有一些代码可以计算函数f在指定的区间[a,b]上改变符号的次数.这在C等命令式语言中相当明显:

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的优点在于我们可以根据需要偷看其余的输入.

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...