【问题标题】:Why does Haskell's `transpose` function in Data.List not use `head` and `tail`?为什么Data.List中Haskell的`transpose`函数不使用`head`和`tail`?
【发布时间】: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])

它使用列表推导而不是 headtail,我认为这很有趣,但不知道为什么这样做。

为什么最好使用列表理解而不是预定义的 headtail 函数(使用 map,就像我对我的功能?是不是和代码效率有关?

我不一定是 Haskell 的新手,但我也不是专家。话虽如此,更多技术性答案和解释也将不胜感激,因为我希望更多地了解未来语言的复杂性(即使我现在不明白原因,我也会知道我要研究什么)。

【问题讨论】:

  • 因为空列表会出错。
  • 您打错了第一个代码示例(translate vs transpose'。如果您在各种输入(即内部列表的长度不相等)上尝试它),您会发现它由于headtail 是部分崩溃,这就是库版本使用列表推导隐式删除(非初始)空列表的原因。
  • 请注意,headtail 本身是根据模式匹配定义的(就像其他所有内容一样),因此通过直接使用模式匹配,您可以跳过一些间接问题并找到问题的核心.一般来说,您应该更喜欢模式匹配并保留headtail 用于特定用途——而不是相反。
  • 另外两种定义它的方法也适用于锯齿状列表和take 1 . map (take 1) $ .... ((1:undefined):undefined),它们是:trs1 [] = [] ; trs1 xs = concatMap (take 1) xs : trs1 [t | (_:t) &lt;- xs],甚至是trs2 xs = case concatMap (take 1) xs of [] -&gt; [] ; a -&gt; a : trs2 (map (drop 1) xs)
  • 最后一个 (trs2) 确实比第一个效率低一些,因为它不会提前清除空列表(就像第一个一样,库版本也是如此) .

标签: list haskell transpose nested-lists definition


【解决方案1】:

对于 列表,headtail 等函数将出错。请注意,如果你写:

[h | (h:_) <- xss]

那么这等同于map head xss。事实上,上面的列表推导等价于:

let ok (h:_) = pure h
    ok _ = fail "…"
in xss >>= ok

所以如果模式匹配失败,那么我们返回一个fail "" 值。对于列表,这是空列表:

Prelude> fail "" :: [Int]
[]

这对于我们要转置的非矩形列表很重要,例如:

Prelude Data.List> transpose [[1,4,2,5],[1,3], [1,9,5,8]]
[[1,1,1],[4,3,9],[2,5],[5,8]]

它会变身:

[ 1 4 2 5]
[ 1 3 ]
[ 1 9 5 8]

到:

[1 1 1]
[4 3 9]
[2 5]
[5 8]

而如果最终使用headtail 计算第三行中的headtail,它将在[1,3] 列表中崩溃:

Prelude Data.List> transpose' [[1,4,2,5],[1,3], [1,9,5,8]]
[[1,1,1],[4,3,9],[2,*** Exception: Prelude.head: empty list

【讨论】:

  • 感谢您的回答,非常详细,我完全理解!我想如果我下次更广泛地测试我的代码,我最终会自己找到答案...... :)
猜你喜欢
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多