【发布时间】:2020-04-11 08:55:47
【问题描述】:
我刚刚花了一些时间来解决我需要以某种方式翻译列表列表的问题。成功完成后,我想出了这个解决方案:
translate :: [[a]] -> [[a]]
translate ([]:xss) = []
translate xss = (map head xss) : (translate $ map tail xss)
不久之后,我意识到我只是在尝试转置矩阵...我想“我可能在尝试这样做时浪费了很多时间,因为 Haskell 肯定在它的标准库来做这样一个常见的操作。”所以我决定检查一下,不出所料,我发现Data.List 模块包含一个transpose 函数。
但真正令我惊讶的是它的定义方式:
transpose :: [[a]] -> [[a]]
transpose [] = []
transpose ([] : xss) = transpose xss
transpose ((x:xs) : xss) = (x : [h | (h:_) <- xss]) : transpose (xs : [ t | (_:t) <- xss])
它使用列表推导而不是 head 和 tail,我认为这很有趣,但不知道为什么这样做。
为什么最好使用列表理解而不是预定义的 head 和 tail 函数(使用 map),就像我对我的功能?是不是和代码效率有关?
我不一定是 Haskell 的新手,但我也不是专家。话虽如此,更多技术性答案和解释也将不胜感激,因为我希望更多地了解未来语言的复杂性(即使我现在不明白原因,我也会知道我要研究什么)。
【问题讨论】:
-
因为空列表会出错。
-
您打错了第一个代码示例(
translatevstranspose'。如果您在各种输入(即内部列表的长度不相等)上尝试它),您会发现它由于head、tail是部分崩溃,这就是库版本使用列表推导隐式删除(非初始)空列表的原因。 -
请注意,
head和tail本身是根据模式匹配定义的(就像其他所有内容一样),因此通过直接使用模式匹配,您可以跳过一些间接问题并找到问题的核心.一般来说,您应该更喜欢模式匹配并保留head和tail用于特定用途——而不是相反。 -
另外两种定义它的方法也适用于锯齿状列表和
take 1 . map (take 1) $ .... ((1:undefined):undefined),它们是:trs1 [] = [] ; trs1 xs = concatMap (take 1) xs : trs1 [t | (_:t) <- xs],甚至是trs2 xs = case concatMap (take 1) xs of [] -> [] ; a -> a : trs2 (map (drop 1) xs)。 -
最后一个 (
trs2) 确实比第一个效率低一些,因为它不会提前清除空列表(就像第一个一样,库版本也是如此) .
标签: list haskell transpose nested-lists definition