簡體   English   中英

在 Haskell 中理解這個矩陣轉置函數

[英]Understanding this matrix transposition function in 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]]

我不明白串聯運算符如何使用地圖。 它在同一個函數調用中連接 x 的每個頭部? 如何?

這是

(map head x)

創建每個列表的頭元素列表?

讓我們看看該函數對您的示例輸入做了什么:

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)

創建每個列表的頭元素列表?

是的。

cons 運算符:將 a 類型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]]

所以 cons-ing [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]]

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

這些東西是一樣的:

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]

這很簡單

順便說一句,當給定[[1,2,3], [], [1,2]]之類的輸入時,此功能不起作用。 但是,來自Data.List的轉置函數將接受此輸入,並返回[[1,1], [2,2], [3]]

當我們調用遞歸transpose代碼時,我們需要去掉[]

如果您想使用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)

顯然,它也適用於方形數組和單例,但是當標准實現為您提供更多選擇時,我看不到它有多大用處。

或者,您可以只定義自己的函數。 我找到了一種更簡單的方法來在 Haskell 中僅使用列表理解來實現矩陣轉置。 它適用於具有非空元素的通用m* n矩陣

transpose' :: [[a]] -> [[a]]
transpose' xss = chop nrows [xs !! (j-1) | i <- [1..ncols], j <- take nrows [i, i+ncols ..]] 
                where nrows = length  xss
                      ncols = length(head xss) 
                      xs = concat xss 

chop :: Int -> [a] -> [[a]]
chop _ [] = []
chop n xs = take n xs : chop n (drop n xs) 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM