现在我将我的数学解决方案从c#移植到Haskell,正在学习Haskell。我有以下Thompson算法代码:
xi[N] = a[N] / c[N];
eta[N] = f[N] / c[N];
for (int i = N - 1; i > 0; i--)
{
var cbxip = (c[i] - b[i] * xi[i + 1]);
xi[i] = a[i] / cbxip;
eta[i] = (f[i] + b[i] * eta[i + 1]) / cbxip;
}
{
int i = 0;
var cbxip = (c[i] - b[i] * xi[i + 1]);
eta[i] = (f[i] + b[i] * eta[i + 1]) / cbxip;
}
我如何在Haskell中执行此操作? 我找到了关于数组初始化的信息,但我有几个问题。
说,我写了以下代码:
xi = [a[i] / (c[i] - b[i] * xi[i + 1]) | i <- 1..N-1] ++ [a[N] / c[N]]
etha = [(f[i] + b[i] * etha[i + 1] / (c[i] - b[i] * xi[i + 1]) | i <- 0..N-1] ++ [f[N] / c[N]]
问题如下:
如何指定我必须初始化数组开始吧?我是否需要这样做,或者Haskell会自己掌握它?如果是后者,它怎么能这样做?对于编译器来说它不仅仅是[f(i)|i<-[a..b]]
的黑盒子吗?
(最有问题的)对于i
中的所有[1..N-1]
,部分(c[i] - b[i] * xi[i + 1])
将被评估两次。我怎样才能解决这个问题?事先将其映射到其他阵列会耗费内存,因为我还没有xi
阵列,所以不可能。
我想到了同步映射之类的东西,但我对如何将它应用于数组初始化感到困惑。
答案 0 :(得分:1)
在你真正熟悉通过递归解决问题之前,我可能会避免使用列表推导。 Haskell与C#非常不同,因为你没有这样的“数组”,可以随机访问和插入 - 你不能预先分配这个空间,因为分配是副作用。相反,要考虑所有要链接的列表,并使用递归来迭代它们。
如果我们从自上而下的方法开始,我们有一堆数字列表,我们需要一个函数来迭代它们。如果我们单独传递这些,我们最终会得到像[n] -> [n] -> [n] -> [n] -> [n] -> ..
这样的函数签名。这可能不是一个好主意,因为它们似乎都是相同的大小,N。相反,我们可以使用元组(或元组对)来包含它们,例如。
thompson :: Num n => [(n, n, n, n, n, n)] -> [(n, n)]
thompson [] = [] -- pattern to terminate recursion for empty lists
-- these variables are equivalent to your a[i], etc in C#
thompson ((a, b, c, f, xi, eta):_) = ?
如果我们完全复制你的C#,我们可能想要列表中2个元素的模式,因为似乎每次迭代都需要访问当前和下一个元素。对于2个或更多元素。
-- handle final 2 elements
thompson ((a, _, c, f, xi, eta):[]) = ((a / c), (f / c))
thompson ((a0, b0, c0, f0, xi0, eta0):(_,_,_,_,xi1,eta1):[]) = ?
-- handle the regular case.
thompson ((a0, b0, c0, f0, xi0, eta0):(a1,b1,c1,f1,xi1,eta1):tail) = ?
一旦拥有了整体迭代结构,如何实现循环中的内容将变得更加明显。循环基本上是一个函数,它接受这些元组中的一个,加上下一个xi / eta的元组并进行一些计算,返回xi / eta的新元组(或者在最后的情况下,只是eta)。 a,b,c,f似乎没有变化。
doCalc1 :: Num n => (n, n, n, n, n, n) -> (n, n) -> (n, n)
doCalc1 (a, b, c, f, xi0, eta0) (xi1, eta1) = (a / cbxip, f + b * eta1 / cbxip)
where cbxip = c - b * xi1
doCalc2 :: Num n => Num n => (n, n, n, n, n, n) -> (n, n) -> n
doCalc2 (a, b, c, f, xi0, eta0) (xi1, eta1) = f + b * eta1 / cbxip
where cbxip = c - b * xi1
现在我们只需要更新thompson来调用doCalc1 / doCalc2,并以尾部递归调用自身。
thompson (head:next@(_,_,_,_,xi,eta):[])
= (xi, doCalc2 head (xi, eta)) : thompson [next]
thompson (head:next@(_,_,_,_,xi,eta):tail)
= doCalc1 head (xi, eta) : thompson (next:tail)