【问题标题】:Haskell - finding k'th element from end, and haskell's can't match the patternHaskell - 从末尾找到第 k 个元素,而 haskell 无法匹配模式
【发布时间】:2015-04-11 15:53:42
【问题描述】:

我正在尝试实现一个递归函数,该函数从末尾返回 k 的元素。

这是我的尝试:

kElementFromEnd :: Int -> [x] -> x

kElementFromEnd _ [] = error "cannot request k item from empty list"
kElementFromEnd k [x]
    | k < 0 = error "k must be non negative"
    | k == 0 = last [x]
    | otherwise = kElementFromEnd (k-1) (init [x])

这是我收到的错误:

*Main> kElementFromEnd 2 [1,2,3]
*** Exception: EX2.hs:(4,1)-(8,54): Non-exhaustive patterns in function kElementFromEnd

我真的不明白为什么 haskell 不能匹配模式。我不明白发生了什么?

谢谢

【问题讨论】:

  • 你可以让这一切变得更简单! kElemFE n xs = reverse xs !! n

标签: haskell


【解决方案1】:

您只匹配了空列表 ([]) 和单元素列表 ([x])。我认为您的意思是用简单的xs 替换[x],一个匹配单个元素列表并将该单个值分配给x 的模式,一个匹配任何尚未匹配的列表的模式。这看起来像

kElementFromEnd :: Int -> [x] -> x
-- This pattern matches the empty list
kElementFromEnd _ [] = error "cannot request k item from empty list"
-- This pattern is just a name, so it matches everything else
-- i.e. non-empty lists
kElementFromEnd k xs
    | k < 0 = error "k must be non negative"
    | k == 0 = last xs
    | otherwise = kElementFromEnd (k-1) (init xs)

它可以作为

> kElementFromEnd 0 [1..5]
5
> kElementFromEnd 4 [1..5]
1
> map (\i -> kElementFromEnd i [1..10]) [0..9]
[10,9,8,7,6,5,4,3,2,1]

【讨论】:

  • 但我想取最后一个元素,因为 (x:xs) 的模式不断将其分成头部和尾部。我想要最后和头部。如何向 haskell 发出我正在寻找多元素列表的信号?
  • 谢谢!不过我一定是误会了什么。假设我要编写 kElementFromEnd 而不首先声明它的签名(第一行代码),haskell 怎么能得出 xs 是一个列表的结论?我不应该使用 [x] 符号来表示它是函数体中的列表吗?
  • @vondip [x] 是一种类型,这里使用的x 与您为函数的参数命名无关。该类型完全等同于Int -&gt; [a] -&gt; [a]Int -&gt; [element] -&gt; [element]。当对参数进行模式匹配时,您实际上在做的是对数据类型的构造函数进行模式匹配。列表具有构造函数[] :: [a](:) :: a -&gt; [a] -&gt; [a]。 Haskell 也有特殊的语法糖来处理列表文字 [a, b, c, d],但编译器在内部将其转换为 a:(b:(c:(d:[])))。因此,[x] == x:[].
  • @vondip 如果你不匹配构造函数,你只是在使用它们自己的名字,比如add x y = x + ytake5 xs = take 5 xs。这里使用的名称xyxs 只匹配那里传递的任何值,这是一个命名的包罗万象。您还可以使用_ 语法来匹配您不想将该值绑定到名称的任何值,如const x _ = x。在这种情况下,Haskell 可以确定它是一个列表,因为您在上面调用 last :: [a] -&gt; ainit :: [a] -&gt; [a]。 Haskell 足够聪明,可以发现xs 只能是一个列表。
  • @vondip “Haskell 怎么能得出结论 xs 是一个列表?”好吧,有几种方法:它显然是第一个等式中的一个列表(因为您使用模式 []),它显然是第二个等式中的一个列表(因为您将它传递给 lastinit,它只处理列表)。
【解决方案2】:

编写这样的函数绝对是一个很好的练习。首先,GHC 可以帮助您捕获此类错误。特别是-fwarn-incomplete-patterns 将在您的模式不完整时发出警告,以防止令人讨厌的运行时错误。使用-Werror,您还可以将其设为错误,以确保您不会错过警告。使用-Wall -Werror 进行编译以消除所有可能的警告并不是一个坏习惯。

第二,让练习更难:因为initO(n),你的函数实现是O(kn)(或者更多精确的O(min(k,n) n)。对于非常小的k,这并不重要,但如果k≈n ,你会得到二次性能。所以我建议你尝试找到一个实现

  • O(n)(独立于k),
  • 适用于大型惰性列表;也就是说,以这样一种方式遍历一个列表,如果没有其他东西将列表保存在内存中,则列表开头的元素会被 GC 不断丢弃;特别是,空间复杂度应该与 n 无关。

剧透:

kElementFromEnd k xs = last $ zipWith const xs (drop k xs)

此外,通常最好优雅地失败,因为调用error 意味着您的程序立即存在。所以另一个改进是做类型签名

kElementFromEnd :: Int -> [x] -> Maybe x

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-13
    • 2021-05-22
    相关资源
    最近更新 更多