我们说我有一个简单的递归函数
Z(i,j) = Sum(Z(i, k), k = 0..j-1) + Sum(Z(k, j), k = 0..i-1)
Z(0,0) = 1
如果你把它放在一个表格中,左下角为(0,0),右上角为(i,j),你可以看到,一般来说,所有单元格只依赖于每个单元格它们的左侧和下侧,左上角到右下角线都可以并行计算。
对于像C这样的语言,我可以从左下角开始实现动态编程填充,然后沿着列向上工作:
// for Z[n][m]
for(i = 0; i <= n; i++) {
for(j = 0; j <= m; j++) {
for(k = 0; k < j; k++) {
Z[i,j] += Z(i, k)
}
for(k = 0; k < i; k++) {
Z[i,j] += Z(k, j)
}
}
}
如果我想并行化这个,我可以简单地收集当前工作的对角线的索引,然后在每个索引上调度相应的函数。
在Haskell中,我可以做类似的事情:
z = array ((0,0), (10,10))
[((i, j), 1 + (sum (map (\x -> z ! (i, x)) [0..j-1])) +
(sum (map (\x -> z ! (x, j)) [0..i-1]))) | i <- [0..10], j <- [0..10]]
有一些我不喜欢的事情。 1:它更难阅读而且不太清楚发生了什么。 2:我无法控制执行顺序。我怎么知道Haskell是否以最有效的方式填充阵列?有没有办法在Haskell中实现它,以便它易于阅读并且我可以控制计算流程?
答案 0 :(得分:1)
来自the docs:
${param['A:B']}
在bounds参数和关联列表的索引中是严格的,但在值中是非严格的。
因此,您的表达式将仅计算您实际使用的条目。您的array
实际上是您在问题顶部指定的递归函数的备忘录表,运行时评估顺序自然由代码中的数据依赖项控制。换句话说,由于懒惰,z
将被免费有效计算。
关于代码可读性的问题:我发现你的功能实现比程序实现更具可读性。 Haskell代码与您使用名为 maths 的编程语言提供的规范更接近:虽然您的C代码谈到增加索引和累积总数,但在Haskell中,您实际上是在谈论对递归调用z ! (i, j)
超过一系列值并总结结果。您可以使用列表推导而不是z
来使其看起来更像数学符号:
map