输出L形矩阵避免二次遍历

Avoiding double traversal in outputting a L-shaped matrix

我正在尝试遍历 L Shape 中的列表列表。例如:lShapedTraverse [[1,2,3],[4,5,6],[7,8,9]] 将导致 [[1,2,3,6,9],[4,5,8],[7]]

我有以下算法可以提供所需的输出:

lShapedTraverse :: [[a]] -> [[a]]
lShapedTraverse [] = []
lShapedTraverse [xs] = [xs]
lShapedTraverse (xs:xss) = let (rest, col) = ((map init xss), (map last xss))
                           in (xs ++ col) : lShapedTraverse rest

这是遍历列表的列表 2 次以获得 initlast,我认为可以使用可以在一次遍历中执行 initAndLast 的自定义函数来避免这种情况。

我正在尝试看看我是否可以做一个更有效和惯用的实现 Haskell。

我们可以写成 initAndLast,但这对性能帮助不大 很多,因为每个元素仍然需要做很多工作 结果。

我们真的很想在列表的开头工作,这样我们就可以 只需不断的工作即可获得元素。我们可以 通过用 map reverse 翻转矩阵 left-to-right 来安排它。 现在我们总是使用第一行和第一列。我们只需要 请记住 un-reverse 我们制作行的部分。

-- L shapes from top left to top right then down to bottom right
lShaped :: [[a]] -> [[a]]
lShaped = lShaped' . map reverse

-- L shapes from top right backwards to top left then down to bottom left
lShaped' :: [[a]] -> [[a]]
lShaped' [] = []
lShaped' ([]:_) = []
lShaped' (xs:xss) = (reverse xs ++ map head xss) : lShaped' (map tail xss)

我们需要两个基本案例来处理比实际高度高的矩形 比身高还宽——你的代码少了一个 其中

或者我们可以尝试使用库函数而不是做 手动递归。

此函数沿 upward-sloping 线将矩形切成两部分。 n 是 upper/left 部分第一行的长度,或者如果 n 更大 比矩形的宽度你必须把它想象成一个坐标 在定义切割的 top-right 点的矩形之外 行,这样一些完整的行将出现在之前的 upper/left 部分 我们切入正题。

slice :: Int -> [[a]] -> ([[a]], [[a]])
slice n xss = unzip (zipWith splitAt [n,n-1 ..] xss)

使用 slice 可以很好地分割元素的水平和 Ls 的垂直部分,但垂直部分未排列成 有用的方法。我们可以再次使用 slice 而不是尝试重新排列它们 在矩阵的转置上使它们进入正确的列表。 最后我们将水平和垂直部分放在一起 zipWith (++).

lShaped'' :: [[a]] -> [[a]]
lShaped'' [] = []
lShaped'' xss = zipWith (++) rowParts (reverse colParts)
  where
    (rowParts, _) = slice width xss
    (_, colParts) = slice width (transpose xss)
    width = length (head xss)

我不知道我是否比手动递归更喜欢这个解决方案 但就是这样。引入长度总是有点丢脸 和数字到列表算法中,但我看不到更简洁的方法 时刻.

由于有多种表示输入矩阵的可能性,我们可以尝试将“导航”(即元素的选择)与实际矩阵表示分开。

为了实现这一点,我们可以轻松编写一个递归函数,生成要从输入矩阵中提取的二维笛卡尔坐标列表:


{-#  LANGUAGE  TupleSections        #-}

-- returns 2D list of Cartesian coordinates for entries of L-shaped matrix:
coordList :: Int -> [[(Int,Int)]]
coordList n = go n 0 n  where   -- rl: Row Length   sr: Starting Row
    go n sr rl = ((map (sr,) [0..(rl-1)]) ++ (map (,rl-1) [(sr+1)..(n-1)]) )  :
                  if (rl > 1)  then  go n (sr+1) (rl-1)  else  []

ghci 解释器下检查:

 λ> 
 λ> coordList 3
 [[(0,0),(0,1),(0,2),(1,2),(2,2)],[(1,0),(1,1),(2,1)],[(2,0)]]
 λ> 

接下来,我们天真地使用低效的 !! 来测试我们的新 coordList 函数。列表提取运算符:

 λ> printAsLines xs = mapM_  (putStrLn . show)  xs
 λ> 
 λ> xss = [[1,2,3], [4,5,6], [7,8,9]]
 λ> 
 λ> printAsLines $ map (map (\(i,j) -> ((xss !! i) !! j))) $ (coordList 3)
 [1,2,3,6,9]
 [4,5,8]
 [7]
 λ> 

这可能效率低下,但它是正确的。因此,我们可以通过用向量和列表替换列表来获得更高效的版本!运算符由等效向量 !运算符:

import qualified  Data.Vector as  V

-- vector-based version:
lShapedTraverse :: [[a]] -> [[a]]
lShapedTraverse xss = 
    let
         rank   = length (head xss)  -- matrix rank
         pairs  = coordList rank     -- 2D list of Cartesian coordinates
         matrix = V.fromList (map  V.fromList  xss)  -- 2D vector
    in
         map  (map (\(i,j) -> ((matrix V.! i) V.! j)))  $  pairs

测试程序:

printAsLines :: Show α => [α] -> IO ()
printAsLines xs = mapM_  (putStrLn . show)  xs


main :: IO ()
main = do
    let  xss   = [[1,2,3], [4,5,6], [7,8,9]]
         lMat1 = lShapedTraverse  xss

    putStrLn $ "res1 = "
    printAsLines lMat1

程序输出:

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