【问题标题】:Better way to write this function: list to matrix (= list of lists)编写此函数的更好方法:列表到矩阵(=列表列表)
【发布时间】:2017-06-17 18:09:17
【问题描述】:

有没有更好的方法来编写这个函数(即通过折叠在一行中)?

--              list -> rowWidth -> list of lists
listToMatrix :: [a] -> Int -> [[a]]
listToMatrix [] _ = []
listToMatrix xs b = [(take b xs)] ++ (listToMatrix (drop b xs) b)

【问题讨论】:

    标签: list haskell matrix


    【解决方案1】:

    其实这是一个很好的展开案例。 Data.List 方法 unfoldr 与折叠列表不同,它通过对种子值应用函数直到此函数返回 Nothing 来创建从种子值到 a 的列表。在我们达到将返回Nothing 的终止条件之前,该函数返回Just (a,b),其中a 是列表的当前生成项,b 是种子的下一个值。在这种特殊情况下,我们的种子值是给定的列表。

    import Data.List
    chunk :: Int -> [a] -> [[a]]
    chunk n = unfoldr (\xs -> if null xs then Nothing else Just (splitAt n xs))
    
    *Main> chunk 3 [1,2,3,4,5,6,7,8,9]
    [[1,2,3],[4,5,6],[7,8,9]]
    *Main> chunk 3 [1,2,3,4,5,6,7,8,9,10]
    [[1,2,3],[4,5,6],[7,8,9],[10]]
    

    【讨论】:

    • 你也可以用null xs代替xs == [],这样你就可以去掉Eq约束。
    【解决方案2】:

    是的,有更好的方法来编写这个函数。但我不认为把它变成一条线会改善任何事情。

    使用前置运算符 (:) 而不是列表连接 (++) 进行单元素前置

    表达式[(take b xs)] ++ (listToMatrix (drop b xs) b) 效率低下。我并不是说在性能方面效率低下,因为编译器可能会优化它,但是,在这里,您正在构造一个列表,然后在其上调用一个函数 ((++)),它将通过模式匹配来解构它。您可以改为使用(:) 数据构造函数直接构建您的列表,它允许您将单个元素添加到您的列表中。表达式变为take b xs : listToMatrix (drop b xs) b

    使用splitAt 避免两次遍历列表

    import Data.List (splitAt)
    
    listToMatrix :: [a] -> Int -> [[a]]
    listToMatrix xs b = row : listToMatrix remaining b
        where (row, remaining) = splitAt b xs
    

    使用Maybe 确保数据的正确性

    listToMatrix :: [a] -> Int -> Maybe [[a]]
    listToMatrix xs b
        | length xs `mod` b /= 0 = Nothing
        | null xs   = Just []
        | otherwise = Just $ row : listToMatrix remaining b
        where (row, remaining) = splitAt b xs
    

    您甚至可以通过定义一个辅助函数来避免每次检查您是否拥有正确数量的元素:

    listToMatrix :: [a] -> Int -> Maybe [[a]]
    listToMatrix xs b
        | length xs `mod` b /= 0 = Nothing
        | otherwise = Just (go xs)
        where go [] = []
              go xs = row : go remaining
                  where (row, remaining) = splitAt b xs
    

    使用安全类型确保数据的正确性

    矩阵的每一行有相同数量的元素,而嵌套列表不允许确保这种条件。为了确保每一行都有相同数量的元素,您可以使用 matrixhmatrix 等库

    【讨论】:

    • 您正确地建议使用splitAt 来避免不必要的额外遍历,然后使用length,这会导致不必要的额外遍历。另一种方法是在remaining 为空的情况下检查length row == b,即最后一行是否具有预期的行长度。
    • Rein Henrichs:是的,但这是否意味着该函数会执行每次递归,直到最后一行发现元素数量不匹配?我的解决方案允许在第一次迭代时立即检测到这些情况。
    • 检查长度不允许“立即”检测到故障:它允许在遍历整个列表后检测到故障。无论哪种方式,您都必须遍历列表。递归很便宜,而额外的时间遍历整个列表很昂贵。
    • 另外,作为一般规则,我不喜欢为了让失败路径更快而让成功路径变慢。
    • 递归如何便宜?我们使用 splitAt 遍历列表的第一个 b 元素。如果我们给出一个长度为nlistToMatrix 的列表,其中b 不分割n,我们将不得不遍历div n b * b 元素。或者这是一个懒惰的把戏?
    【解决方案3】:

    不,虽然我希望chunksOf 在标准库中。如果您愿意,可以从这里获得:https://hackage.haskell.org/package/split-0.2.3.2/docs/Data-List-Split.html

    请注意,更好的定义是:

    listToMatrix xs b = (take b xs) : (listToMatrix (drop b xs) b)
    

    虽然这可能会编译到同一个核心。您也可以使用splitAt,尽管这很可能再次执行相同的操作。

    listToMatrix xs b = let (xs,xss) = splitAt b xs in xs : listToMatrix xss
    

    【讨论】:

      【解决方案4】:

      你可以使用这个,想法是获取主列表的所有块,这是一种不同的方法,但基本上是一样的:

      Prelude> let list2Matrix n xs = map (\(x ,y)-> (take n) $ (drop (n*x)) y) $ zip [0..] $ replicate (div (length xs)  n) xs
      Prelude> list2Matrix 10 [1..100]
      [[1,2,3,4,5,6,7,8,9,10],[11,12,13,14,15,16,17,18,19,20],[21,22,23,24,25,26,27,28,29,30],[31,32,33,34,35,36,37,38,39,40],[41,42,43,44,45,46,47,48,49,50],[51,52,53,54,55,56,57,58,59,60],[61,62,63,64,65,66,67,68,69,70],[71,72,73,74,75,76,77,78,79,80],[81,82,83,84,85,86,87,88,89,90],[91,92,93,94,95,96,97,98,99,100]]
      

      【讨论】:

        猜你喜欢
        • 2017-09-11
        • 2019-02-10
        • 1970-01-01
        • 2021-09-09
        • 1970-01-01
        • 1970-01-01
        • 2017-06-24
        • 2022-01-03
        • 1970-01-01
        相关资源
        最近更新 更多