在Haskell中从矩阵中提取对角线的最佳方法是什么?

时间:2010-10-22 16:27:30

标签: algorithm haskell matrix

我被要求编写一个函数来提取存储为列表列表的矩阵的对角线。第一个版本是通过索引列表来提取数字,但我很快得出结论,它对Haskell来说不是一个好的算法并写了另一个函数:

getDiagonal :: (Num a) => [[a]] -> [a]
getDiagonal [[]]       = []
getDiagonal (xs:[])    = [head xs]
getDiagonal (x:xs)     = head x : getDiagonal (map tail xs)

由于我刚刚开始学习Haskell,我不确定它是用惯用的方式写的还是表现不错。

所以我的问题是有没有更好的方法从存储在这种表示中的矩阵中提取对角线,或者如果没有更好的算法可以构造,如果矩阵是使用更高阶的Haskell概念表示的,比如代数类型? 在模式匹配中解构列表如何((x:_):xs)或者如上所示的head函数之间是否有任何性能差异?

编辑:实际上更多是好奇的探究而不是作业,他们不在这里教授技术大学的函数式编程(我觉得这很糟糕),但我会留下标签。

3 个答案:

答案 0 :(得分:16)

我认为使用索引是可以的,如果你可以假设参数是一个方阵。无论如何,使用此表示形式的对角线是O(N 2 ),因为您必须遍历列表。

diag x = zipWith (!!) x [0..]

答案 1 :(得分:11)

您可以将原始定义简化为:

mainDiagonal :: [[a]] -> [a]
mainDiagonal []     = []
mainDiagonal (x:xs) = head x : getDiagonal (map tail xs)

为此使用索引没有太大的错误,这使您可以进一步简化为:

mainDiagonal xs = zipWith (!!) xs [0..]

基于数组的表示

您还可以使用(i,j)索引的Data.Array来表示矩阵。这使您几乎可以逐字地使用主对角线的数学定义:

import Data.Array

mainDiagonal :: (Ix i) => Array (i, i) e -> [e]
mainDiagonal xs = [ e | ((i,j),e) <- assocs xs, i == j ]

你可以使用它:

-- n×n matrix helper
matrix n = listArray ((0,0),(n-1,n-1))

> mainDiagonal $ matrix 3 [1..]
[1,5,9]

效率

先前对mainDiagonal的定义仍然没有效率:它仍然需要i == j的O(N²)测试。类似于zipWith版本,它可以修复和推广如下:

mainDiagonal xs = (xs !) `map` zip [n..n'] [m..m']
                      where ((n,m),(n',m')) = bounds xs

此版本仅索引数组O(N)次。 (作为奖励,它也适用于矩形矩阵,并且独立于索引基础。)

答案 2 :(得分:3)

sdcwc回答了原来的问题。我想指出,将矩阵表示为列表列表通常效率低下。列表很好,长度未知,矩阵通常是固定大小。您可以考虑使用平面关联列表或映射来构建矩阵,以及在使用此矩阵实际运行计算时具有恒定元素访问时间的任何内容。 Data.Array是一个不错的选择(参见Piet的回答)。

如果在Haskell中运行数值计算,则可以使用hmatrix包。它有自己的矩阵数据类型Data.Packed.Matrix,它有一个takeDiag函数来提取对角线。

Data.Packed.Matrix示例

例如,如果m是您的矩阵

ghci> let m = (3><3) [1..]
ghci> :t m
m :: Matrix Double
ghci> m
(3><3)
 [ 1.0, 2.0, 3.0
 , 4.0, 5.0, 6.0
 , 7.0, 8.0, 9.0 ]
然后你可以像这样提取对角线:

ghci> takeDiag m
3 |> [1.0,5.0,9.0]