【问题标题】:How to do inverse series sum in haskell efficiently?如何有效地在haskell中进行反序列求和?
【发布时间】:2023-03-18 12:42:01
【问题描述】:

这是我试图在 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;
}

【问题讨论】:

  • Haskell 可能会在不实现列表的情况下迭代列表,因此可能不会浪费空间。
  • @Thilo 即使记住这一点,计算仍然需要一点点突变。 我们不在 C 中标记所有 const 的原因。不是争论,而是一切不可变的想法有时让我很痒。但是感谢您消除我的疑问! :)
  • @GauravP - 如果你正在总结 n 的倒数,你应该修复你的 C 代码:sum += 1 / (double) n
  • @GauravP 你低估了编译器可以做的工作量。 Haskell 中的东西默认是不可变的。这减少了开销,因为编译器可以进行 C 编译器无法进行的优化,因为已经保证了不变性。
  • @ErikR 不完全是倒数的总和。它已针对我在作业中遇到的数学问题进行了修改。它需要 'n' 是每次迭代 'i' 中 1 到 'i' 的总和。

标签: haskell


【解决方案1】:

使用scanl1 生成您的ns 列表:

> 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 代码将值视为不可变的,并且看起来您正在生成一堆中间列表,但编译器足够聪明,可以生成高效的 可执行 代码。

【讨论】:

  • 或者,使用数学2-2/(n+1)=1.9
  • 是的,这也适用于原始 C 代码。我们试图指出,C 中的坏主意在 Haskell 中不一定是坏主意。
  • 我正在等待编译器变得足够聪明以执行这些更高级别的优化...
  • 正是我正在寻找的答案。最后的输出也与 C 相同。谢谢! :)
【解决方案2】:

您可以使用-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

【讨论】:

    【解决方案3】:

    要暂时摆脱不变性,您可以使用the ST monad

    但是没有必要这样做。在以下表达式中,由于Stream Fusion,编译器将优化所有内容:

    sum (map (\n -> 1 / fromIntegral n) [1..20])
    

    【讨论】:

    • 请注意,我也在每次迭代中递增 n,因此它会像 1、3、6、10 等等直到 20。i 和 n 同时变化。非常感谢,我一直在寻找这种逃避的不变性。 ST 单子。很好的资源。
    • Data.List 使用 foldr/build fusion,基本上和 stream fusion 完全相反。
    • 我希望我们不要再引用那个答案了。里面的很多信息都已经过时了。例如,默认的 Prelude 列表函数已经有一段时间进行流融合了……this question 上的第一条评论就是这么说的。
    猜你喜欢
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2012-02-21
    • 1970-01-01
    • 2023-03-20
    • 2013-07-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多