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

发布于 2024-09-28 18:58:40 字数 567 浏览 0 评论 0原文

我被要求编写一个函数来提取存储为列表列表的矩阵的对角线。第一个版本是通过索引列表来提取数字,但我很快就得出结论,这对于 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 函数解构列表之间是否存在性能差异?

编辑:实际上更多的是好奇的询问而不是家庭作业,他们在这里的技术大学不教授函数式编程(我认为这很遗憾),但我会留下标签。

I was asked to write a function that would extract the diagonal of a matrix stored as a list of lists. The first version was to extract the number by indexing the lists, but I soon concluded it isn't a good algorithm for Haskell and wrote another function:

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

Since I've only started learning Haskell I'm not sure if it's written in an idiomatic way or if it would perform well.

So my question is is there any better way to extract a diagonal from matrix stored in such a representation or if there isn't is there a better algorithm that could be constructed if the matrix was represented using higher order Haskell concepts, like algebraic types?
Also is there any performance difference between deconstructing the list in pattern matching like so ((x:_):xs) or with the head function like shown above?

EDIT: Actually more of curious inquiry than a homework, they don't teach functional programming at technical universities here (which is a pitty I think), but I'll leave the tag.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

国际总奸 2024-10-05 18:58:40

我认为如果您可以假设参数是方阵,那么使用索引就可以了。无论如何,使用这种表示形式获取对角线的时间复杂度为 O(N2),因为您必须遍历列表。

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

I think using indexing is OK, if you can assume that the argument is a square matrix. Getting diagonal with this representation is O(N2) anyway, since you have to traverse the lists.

diag x = zipWith (!!) x [0..]
好久不见√ 2024-10-05 18:58:40

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

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

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

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

基于数组的表示

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

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 定义仍然效率不高:它仍然需要 O(N²) 次测试 我==j。类似于 zipWith 版本,它可以被修复并概括如下:

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

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

You can simplify your original definition to:

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

There isn't much wrong with using indexing for this, which lets you simplify it further to:

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

Array-based representation

You can also represent matrixes using Data.Array indexed by (i,j). This lets you use the mathematical definition of the main diagonal almost verbatim:

import Data.Array

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

You can use this like:

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

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

Efficiency

The previous definition of mainDiagonal is still not efficient: it still needs O(N²) tests of i == j. Analogous to the zipWith version, it can be fixed and generalized as follows:

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

This version only indexes the array O(N) times. (As a bonus, it also works with rectangular matrices, and is independent of the indexing base.)

关于从前 2024-10-05 18:58:40

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]

sdcwc has answered the original question. I'd like to note that representing the matrix as a list of lists is usually inefficient. Lists are good where the length is unknown, matrices are usually of the fixed size. You may consider using a flat associative list or map to build the matrix, and anything with constant element access time when you actually run calculations with this matrix. Data.Array is a good choice (see Piet's answer).

If you run numerical calculations in Haskell, you can use hmatrix package. It has its own matrix data type, Data.Packed.Matrix, and it has a takeDiag function to extract the diagonal.

Data.Packed.Matrix example

For example, if m is your matrix

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 ]

then you can extract its diagonal like this:

ghci> takeDiag m
3 |> [1.0,5.0,9.0]
ˉ厌 2024-10-05 18:58:40

如果性能不那么相关并且您选择这种方法,只需补充一点 Delport 的答案:

diagonal :: [[a]] -> [a]
diagonal [] = []
diagonal (mHead:mTail) = head mHead:diagonal (map tail mTail)

应该注意的是,由于 head 和 <,该函数只能与您知道是方阵的矩阵一起使用代码>尾用法。

对于 n X m 矩阵,您可以实现忽略额外值的函数,使 [[1,2,3], [4,5,6]] 返回 < code>[1,5]:

diagonal [] = []
diagonal ([]:_) = []
diagonal ((rHead:_):mTail) = rHead:diagonal (map tail mTail)

或者实现一个当你的矩阵不是正方形时会抛出错误的版本:

diagonal [] = []
diagonal ([]:_) = error "not a square matrix"
diagonal [x:y:z] = error "not a square matrix"
diagonal ((rHead:_):mTail) = rHead:diagonal (map tail mTail)

对于这两个答案,应该注意的是,如果你的“列表”,它们仍然可能会抛出“意外”错误由于 map tail mTail 调用,lists' 不是矩阵/不统一 ([[1,2], [1]])。

Just complementing Delport's answer a little bit, if performance is not that relevant and you choose this approach:

diagonal :: [[a]] -> [a]
diagonal [] = []
diagonal (mHead:mTail) = head mHead:diagonal (map tail mTail)

It should be noted that this function can only be used with matrices you know are square, due to head and tail usage.

For n X m matrices you can either implement the function to ignore extra values, making [[1,2,3], [4,5,6]] return [1,5]:

diagonal [] = []
diagonal ([]:_) = []
diagonal ((rHead:_):mTail) = rHead:diagonal (map tail mTail)

Or implement a version that will throw an error when your matrix is not square:

diagonal [] = []
diagonal ([]:_) = error "not a square matrix"
diagonal [x:y:z] = error "not a square matrix"
diagonal ((rHead:_):mTail) = rHead:diagonal (map tail mTail)

For both answers, it should be noted that they might still throw 'unexpected' errors if your 'list of lists' is not a matrix/not uniform ([[1,2], [1]]) due to the map tail mTail call.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文