如何有效地在haskell中进行逆序列求和?

时间:2016-08-20 13:13:20

标签: haskell

这是我试图在haskell中转换的代码。但不变性造成了问题。如何在没有可变变量的情况下进行计算。创建[1..20]列表并在其上进行映射似乎不太节省空间。是否有任何技巧或技巧可以逃避不变性?

int main()
{
    int i,n=0;
    double sum= 0.0;
    for ( i=1; i<20; ++i )
    {
        n += i;
        sum = 1 / (double) n;
    }
    printf("The sum is: %lf",sum);
    return 0;
}

3 个答案:

答案 0 :(得分:5)

使用scanl1生成n s:

列表
> scanl1 (+) $ [1..19]
[1,3,6,10,15,21,28,36,45,55,66,78,91,105,120,136,153,171,190]

从那里,计算反转是一件简单的事情

> map (1/) . scanl1 (+) $ [1..19]
[1.0,0.3333333333333333,0.16666666666666666, ...]

并总结:

> sum . map (1/) . scanl1 (+) $ [1..19]
1.8999999999999997

虽然 Haskell 代码将值视为不可变的,并且看起来您正在生成一堆中间列表,但编译器足够智能以生成有效的可执行代码。

答案 1 :(得分:4)

您可以使用-ddump-asm检查GHC生成的代码。

编译此程序:

main = print $ 1.0 / fromIntegral (sum [(1::Int)..12345])

使用:

$ ghc -O2 Main.hs -ddump-asm > asm-output

然后查看asm-output,搜索12345,您会看到此循环:

_c47b:
        addq %r14,%rsi
        incq %r14
_c476:
        cmpq $12345,%r14
        jne _c47b

这表明实际上并未创建列表[1..12345]

<强>更新

似乎你打算将三角数的倒数相加, 即1/1 + 1/3 + 1/6 + ......也就是说,你打算写:

sum += 1.0 / (double) n;

这可以在Haskell中表示为:

main = print $ sum $ map (\x -> 1 / (fromIntegral x)) $ scanl (+) 1 [(2::Int)..12345]

检查生成的程序集,我们再次看到没有创建中间列表:

_c4ao:
        cvtsi2sdq %rsi,%xmm0
        movsd _n4aP(%rip),%xmm2
        divsd %xmm0,%xmm2
        addsd %xmm2,%xmm1
        incq %r14
_c4ad:
        addq %r14,%rsi
        cmpq $12345,%r14
        jne _c4ao

此处%r14是您的C代码中的计数器i%rsi是变量n%xmm1是累加器sum

答案 2 :(得分:3)

要暂时避开不变性,可以使用the ST monad

然而,没有必要这样做。在下面的表达式中,由于Stream Fusion

,编译器将优化所有内容
sum (map (\n -> 1 / fromIntegral n) [1..20])