控制Haskell中的数组填充

时间:2017-01-01 23:52:47

标签: haskell

我们说我有一个简单的递归函数

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中实现它,以便它易于阅读并且我可以控制计算流程?

1 个答案:

答案 0 :(得分:1)

来自the docs

  

${param['A:B']}在bounds参数和关联列表的索引中是严格的,但在值中是非严格的。

因此,您的表达式将仅计算您实际使用的条目。您的array实际上是您在问题顶部指定的递归函数的备忘录表,运行时评估顺序自然由代码中的数据依赖项控制。换句话说,由于懒惰,z将被免费有效计算。

关于代码可读性的问题:我发现你的功能实现比程序实现更具可读性。 Haskell代码与您使用名为 maths 的编程语言提供的规范更接近:虽然您的C代码谈到增加索引和累积总数,但在Haskell中,您实际上是在谈论对递归调用z ! (i, j)超过一系列值并总结结果。您可以使用列表推导而不是z来使其看起来更像数学符号:

map