【问题标题】:Pick the second last element of the list选择列表的倒数第二个元素
【发布时间】:2015-07-17 20:50:43
【问题描述】:

一段时间后,我决定重新学习一些函数式编程。这次我决定选择 Haskell,因为它的特性和 .. 语法。

目前我正在做一些练习,但我被卡住了。我想编写一个从列表中选择倒数第二个元素的函数,即给定 [1,2,3,4] 它将是 3。

这是我的功能。

lastButOne xs 
    | null xs = xs 
    | length xs == 1 = xs 
    | length xs == 2 = lastButOne head xs
    | otherwise = lastButOne tail xs

不幸的是,它会产生一些错误。

Couldn't match expected type `[a]' with actual type `[a1] -> [a1]'
Relevant bindings include
  xs :: [a] (bound at lastButOne.hs:1:12)
  lastButOne :: [a] -> [a] (bound at lastButOne.hs:1:1)
Probable cause: `tail' is applied to too few arguments
In the first argument of `lastButOne', namely `tail'
In the expression: lastButOne tail xs

我尝试过一些部分性,例如 (head xs) 和 (tail xs),但没有帮助。

   Occurs check: cannot construct the infinite type: a ~ [a]
   Expected type: [[a]]
     Actual type: [a]
   Relevant bindings include
     xs :: [a] (bound at lastButOne.hs:1:12)
     lastButOne :: [a] -> [a] (bound at lastButOne.hs:1:1)
   In the first argument of `head', namely `xs'
   In the first argument of `lastButOne', namely `(head xs)'

跟进: 或者我应该写后续的。好吧,我最初的想法是编写一个函数,如果列表长度为 1,则该函数会产生头元素。因此,鉴于 Lee 的解释,很容易得出以下结论:

lastPattern :: [a] -> Maybe a
lastPattern [] = Nothing
lastPattern [x] = Just x
lastPattern [x,_] = Just x
lastPattern (_:xs) = lastPattern xs

这是第一个问题。 条件 [x] 和 [x,_] 的模式是什么

接下来我想做的是用相反的方式编写相同的函数(正如 Paul Johnson 所指出的)。我很快想出了head (tail (reverse [1,2,3,4])),它似乎在 REPL 中运行良好。但是当我开始一些编码时,我最终得到了

 lastRev :: [a] -> a
lastRev xs 
    | null xs = error "empty list"
    | length xs == 1 = error "too short"
    | otherwise = head (tail (reverse xs))

因为 head 函数是 head :: [a] -> a 。上面的功能对我的口味来说有点乱,可以这么说。 有什么办法可以让我成为:: [a] -> Maybe a 吗?这是第二个问题。

最后但并非最不重要的 - 第三个问题。 哪个函数在性能方面更好?我如何在 Haskell 中测量它?

【问题讨论】:

  • 有什么理由不能使用“reverse”?
  • 没有特别的原因。正如我所写,我只是在学习,我想实现这种“我的”方式,而不是“最好的”或“正确的”方式。我只是想更好地理解语言。也许我会在几分钟内用反向功能解决这个问题。所以谢谢你指出这一点。 :)

标签: haskell


【解决方案1】:

(head xs) 返回一个a,而您正试图将它传递给lastButOne,这需要[a] 参数。在这种情况下,您可以直接返回 head xs。前两种情况也有问题,因为它们返回一个列表,而需要一个元素。由于在这种情况下没有这样的元素,您可能会返回错误:

lastButOne :: [a] -> a
lastButOne xs 
    | null xs = error "empty list"
    | length xs == 1 = error "list too short"
    | length xs == 2 = head xs
    | otherwise = lastButOne (tail xs)

更实用的解决方案是在函数类型中编码偏向性并返回Maybe a,因此如果输入列表太短,您可以返回Nothing

lastButOne :: [a] -> Maybe a
lastButOne xs 
    | null xs = Nothing
    | length xs == 1 = Nothing
    | length xs == 2 = Just (head xs)
    | otherwise = lastButOne (tail xs)

最后,更好的解决方案是使用模式匹配而不是保护长度:

lastButOne :: [a] -> Maybe a
lastButOne [] = Nothing
lastButOne [_] = Nothing
lastButOne [x,_] = Just x
lastButOne (_:xs) = lastButOne xs

【讨论】:

  • lst !! (length lst - 2) 作为第三种情况有什么问题?节省你的递归
  • 感谢您的回答。我还没有阅读关于模式匹配的内容,这就是我使用守卫的原因。
  • @ElectricCoffee,它比模式匹配版本更慢,而且总体上也不那么明显。
  • 如果!! 是随机访问,xs !! (length xs - 2) 可能会更有效。 Haskell 的标准[] 数据类型表示递归访问-O(n) 链表数据结构。 length 是 Θ(n)。 !!length 都是使用递归定义的。不要避免 Haskell 中的递归;了解何时以及如何使用它。
【解决方案2】:

这里有多个错误。

首先,调用语法绑定到左边,这意味着lastButOne head xs的意思是“用两个参数调用lastButOneheadxs”,而不是“用调用结果调用lastButOneheadxs"。

其次,你的函数返回一个列表,即使你的描述说它应该返回一个元素。

第三,调用lastButOne (head xs) 也是类型错误,因为head 返回单个元素,但lastButOne 需要一个列表。我相信您的意思是在这里简单地写head xs

但是你会遇到第四个错误,即你的函数的前两个分支实际上返回了整个列表,如果你想返回单个元素,它们就不能返回。

您还需要考虑函数应该在它不是至少两个元素时返回什么。它应该像head 那样崩溃吗?还是应该将结果类型实际更改为Maybe a 并返回Nothing

【讨论】:

    【解决方案3】:

    我是 Haskell 的新手,但这是我所做的解决方案:

    lastButOne xs = if null xs || length xs == 1
                then []
                else
                    if length xs == 2
                    then head xs
                    else lastButOne (tail xs)
    

    【讨论】:

    • 你明白为什么这很慢吗?
    • 提示:你打电话给length
    猜你喜欢
    • 1970-01-01
    • 2011-07-22
    • 2017-02-11
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 2011-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多