在Haskell中理解这个矩阵转置函数

时间:2010-04-05 14:40:11

标签: algorithm list haskell

这个矩阵转置函数有效,但我试图理解它的逐步执行,我不明白。

    transpose:: [[a]]->[[a]]
    transpose ([]:_) = []
    transpose x = (map head x) : transpose (map tail x)

transpose [[1,2,3],[4,5,6],[7,8,9]]

它返回:

 [[1,4,7],[2,5,8],[3,6,9]]

我不知道连接运算符如何使用map。它是在同一函数调用中连接x的每个头?怎么样?

是这个

(map head x)

创建每个列表的头元素列表?

6 个答案:

答案 0 :(得分:29)

让我们看一下函数对你的示例输入的作用:

transpose [[1,2,3],[4,5,6],[7,8,9]]
<=>
(map head [[1,2,3],[4,5,6],[7,8,9]]) : (transpose (map tail [[1,2,3],[4,5,6],[7,8,9]]))
<=>
[1,4,7] : (transpose [[2,3],[5,6],[8,9]])
<=>
[1,4,7] : (map head [[2,3],[5,6],[8,9]]) : (transpose (map tail [[2,3],[5,6],[8,9]]))
<=>
[1,4,7] : [2,5,8] : (transpose [[3],[6],[9]])
<=>
[1,4,7] : [2,5,8] : (map head [[3],[6],[9]]) : (transpose (map tail [[3],[6],[9]]))
<=>
[1,4,7] : [2,5,8] : [3, 6, 9] : (transpose [[], [], []])
<=>
[1,4,7] : [2,5,8] : [3, 6, 9] : [] -- because transpose ([]:_) = []
<=>
[[1,4,7],[2,5,8],[3,6,9]]

请注意,我选择缩减条款的顺序与haskell将使用的评估顺序不同,但这不会改变结果。

编辑:回复您编辑过的问题:

  

是这个

(map head x)
     

创建每个列表的头元素列表?

是的,是的。

答案 1 :(得分:3)

cons运算符:将类型为a的对象附加到类型[a]的列表中。在

(map head x) : transpose (map tail x)

LHS是一个列表(a = [b]),而RHS是列表([a] = [[b]])的列表,因此这样的结构是有效的。结果是

[x,y,z,...] : [[a,b,...],[d,e,...],...] = [[x,y,z,...], [a,b,...],[d,e,...],...]

在您的情况下,map head xmap tail x会拆分矩阵

x = [[1,2,3],[4,5,6],[7,8,9]]

map head x = [1,4,7]
map tail x = [[2,3],[5,6],[8,9]]

(是的,map head x是每个列表的头元素列表。)第二部分是转置(详细步骤见@sepp2k的答案)以形成

transpose (map tail x) = [[2,5,8],[3,6,9]]

因此,[1,4,7]包含

map head x : transpose (map tail x) =  [1,4,7] : [[2,5,8],[3,6,9]]
                                    = [[1,4,7] ,  [2,5,8],[3,6,9]]

答案 2 :(得分:3)

ghci是你的朋友:

*Main> :t map head
map head :: [[a]] -> [a]
*Main> :t map tail
map tail :: [[a]] -> [[a]]

即使您不理解map(您想要快速纠正的问题!),这些表达式的类型也会说明它们的工作原理。第一个是从列表列表中获取的单个列表,所以让我们向它提供一个简单的向量,看看会发生什么。

你可能想写

*Main> map head [1,2,3]

但未能进行类型检查:

<interactive>:1:14:
    No instance for (Num [a])
      arising from the literal `3' at :1:14
    Possible fix: add an instance declaration for (Num [a])
    In the expression: 3
    In the second argument of `map', namely `[1, 2, 3]'
    In the expression: map head [1, 2, 3]

请记住,参数的类型是列表,所以

*Main> map head [[1,2,3]]
[1]

变得更复杂

*Main> map head [[1,2,3],[4,5,6]]
[1,4]

使用tail而不是head执行相同操作

*Main> map tail [[1,2,3],[4,5,6]]
[[2,3],[5,6]]

正如您所看到的,transpose的定义是使用map head x反复切割第一个“行”并将其余部分转换为map tail x

答案 3 :(得分:1)

这些都是一样的:

map head xxs
map (\xs -> head xs) xxs

它是lambda表达式,这意味着我为每个xs返回xs的头部 例如:

   map head [[1,2,3],[4,5,6],[7,8,9]]
-> map (\xs -> head xs) [[1,2,3],[4,5,6],[7,8,9]]
-> [head [1,2,3], head [4,5,6], head [7,8,9]]
-> [1,4,7]

很简单

答案 4 :(得分:0)

顺便说一句,当给定[[1,2,3], [], [1,2]]之类的输入时,此功能不起作用。但是,Data.List的转置函数会接受此输入,并返回[[1,1], [2,2], [3]]

当我们调用递归transpose代码时,我们需要摆脱[]

答案 5 :(得分:0)

如果您想使用 headtail 转置 矩形 数组,请事先确保列数相同,然后您可以执行以下操作:

rectangularTranspose :: [[a]] -> [[a]]
rectangularTranspose m = rectTrans m []
  where
    rectTrans [] a = a
    rectTrans ([]:xss) a = rectTrans xss a
    rectTrans ((x:xs):xss) a = rectTrans a ((x:map head xss): rectTrans (xs:map tail xss) a)

很明显,它也适用于方形数组和单例,但当标准实现为您提供更多选择时,我认为这没有多大用处。