当前位置: 代码迷 >> 综合 >> Haskell 学习笔记-06:矩阵问题——函数式编程真不容易
  详细解决方案

Haskell 学习笔记-06:矩阵问题——函数式编程真不容易

热度:14   发布时间:2023-12-12 16:29:17.0

函数式编程语言没有循环命令,因此,需要用循环解决的问题一般都用递归代替。但是,很多问题的确不适合递归,比如矩阵的相关操作。我打算探讨一下矩阵有关的算法,看看如何处理这类问题。

练习1 在 N 阶方阵中填写自然数序列,例如:

???147258369??? ( 1 2 3 4 5 6 7 8 9 )

代码如下,用两次列表推导式代替双重循环,实现还算简单:

mat123 n = let row123 n k = [x|x <- [k.. k+n-1]]in [row123 n k| k <- [1,1+n..n*n-n+1]]

写完把这段代码,我觉得和 C 语言比还是有些不舒服,所以停下来仔细比较一下二者的差别,然后再看看是否有更好的实现方法。C 语言(伪代码,不想在代码上浪费时间了)代码如下:

int[][] mat123(int n){int [n][n]resultt = 1for (int i = 0; i < n; ++i){for (int j = 0; j < n; ++j) {result[i][j] = t;++t;}}return result
}

和前面的 Haskell 代码相比,实际上变量 t 扮演了全局变量的角色,生命周期存活在两个循环之外。C 语言代码行数较多,但是计算过程比较容易理解,就是一篇通俗小说。Haskell 程序虽短,但理解难度较大,设计难度也很大。所以,不能单纯认为函数式编程代码简短,工作量就会很少,这是不对的。一首简短的诗词需要花费的功夫,未必小于一篇通俗小说。

函数式编程如果也能实现一个和 C 语言风格相近的设计, 有利于两种语言思维的转换,也有利于理解两种编程模型内在的区别。另外,我不太认同那种一味追求最短的代码的做法,代码易读性应该是第一位的。

先来分析一下问题的难点。首先把 C 代码简化如下:

int[][] mat123(int n){int [n][n]resultt = 1for (int i = 0; i < n; ++i){result[i] = row()++t}return result

这个 row() 函数就是原来的 for (int j = 0;…) 那个循环,它本质上有三个全局变量,n,t 和 i。因此,for (int i…)这个循环每一轮调用 row() ,其返回结果都不一样。这一特征是典型的面向过程语言的“副作用”,函数式编程是不允许这种函数存在的,这就造成在 Haskell 中复现这个思路困难重重。下面考虑用 Haskell 模拟这一思路实现算法,代码如下。

mat123' n =let mat n result t = if t == [] then resultelse result ++ [take n t] ++ mat n [] (drop n t)in  mat n [] [1..n*n]

定义了一个临时函数 mat ,把 C 语言中的全局变量result、t 作为参数传递,每次递归调用,利用参数传送修改它们的值。就这个例子而言,找到了处理全局变量的方法,但是感觉这一段代码写的好垃圾哦。

又改进了一下,占用资源少了点:

mat123'' n =let mat n result t = if t >= n*n  then resultelse result ++ [[t+1..t+n]] ++ (mat n [] (t+n))in  mat n [] 0

尝试各种不同的实现方法,反复比较与过程式编程语言的差异,才能对函数式编程的理解逐步深入。

  相关解决方案