Haskell 使用模式查找第n个元素

Haskell 使用模式查找第n个元素,haskell,Haskell,我正在通过向您学习Haskell来跟上Haskell的基础知识。我对函数式编程和模式匹配都很熟悉,但后者更适合Mathematica的工作方式 本着与第4.1章中天真地实施head相同的精神,我继续天真地实施last,如下所示: last1 :: [a] -> a last1 (_:x:[]) = x 但是,调用last1[1,2,3,4]时出现错误异常:。。。函数last1中的非穷举模式。我理解这个错误意味着指定的模式并没有覆盖所有可能的输入,通常需要一个catch-all模式(我没有

我正在通过向您学习Haskell来跟上Haskell的基础知识。我对函数式编程和模式匹配都很熟悉,但后者更适合Mathematica的工作方式

本着与第4.1章中天真地实施
head
相同的精神,我继续天真地实施
last
,如下所示:

last1 :: [a] -> a
last1 (_:x:[]) = x
但是,调用
last1[1,2,3,4]
时出现错误
异常:。。。函数last1中的非穷举模式
。我理解这个错误意味着指定的模式并没有覆盖所有可能的输入,通常需要一个catch-all模式(我没有提供)。然而,我不太清楚为什么我的输入会出现这个错误

问题1:我(对我不正确的方法)的理解是,第一个元素被
\uu
捕获,其余元素被分配到
x
,这与我的初衷不符。但是,这不应该给出一个类型错误吗,因为我指定了
[a]>a
,但是
x
现在是一个列表

请注意,这并不是关于如何编写一个有效的
last
函数-我知道我可以将其编写为(除其他可能性外)

问题2:为了更好地理解Haskell中的模式匹配,我如何使用模式匹配从给定列表中挑选最后一个元素,或者更一般地说,
n
th元素,例如,
[1..10]

建议您可以使用模式匹配将最后一个元素与
ViewPatterns
扩展绑定,但奇怪的是,没有类似于
head的“简单”模式

在Mathematica中,我可能会这样写:

Range[10] /. {Repeated[_, {5}], x_, ___} :> x
(* 6 *)
选择第六个元素,然后

Range[10] /. {___, x_} :> x
(* 10 *)
选择非空列表的最后一个元素


如果这在本文后面讨论,我深表歉意,但我试图将遇到的每个主题和概念与我所知道的其他语言如何处理它们联系起来,以便我能够理解它们的差异和相似性。

如果不使用视图模式,就无法让模式匹配获得“最后”元素。这是因为如果不使用递归(至少是隐式的),就无法获取列表的最后一个元素,而且,也无法确定获取最后一个元素的方法

你的代码

last1 (_:x:[]) = x
应该像

last1 (_:(x:[])) = x
可以去糖化成

last1 a = case a of
               (_:b) -> case b of
                             (x:c) -> case c of
                                           [] -> x
完成本练习后,我们看到了代码的作用:如果列表的最外层构造函数是cons单元格,下一个构造函数是cons,第三个构造函数是nil,那么您已经编写了一个与列表匹配的模式

那么在

last1 [1,2,3,4]
我们有

last1 [1,2,3,4] 
= last1 (1:(2:(3:(4:[]))))
= case (1:(2:(3:(4:[])))) of
       (_:b) -> case b of
                     (x:c) -> case c of
                                   [] -> x
= case (2:(3:(4:[]))) of
       (x:c) -> case c of
                     [] -> x
= let x = 2 in case (3:(4:[])) of
                    [] -> x
= pattern match failure
你的榜样

last1 (_:x:[]) = x
仅匹配包含两个元素的列表,即
a:b:[]
格式的列表
\
匹配不带绑定的列表头,
x
匹配以下元素,空列表匹配自身

当模式匹配列表时,只有最右边的项表示一个列表-匹配列表的尾部

您可以使用如下函数从列表中获取第n个元素:

getNth :: [a] -> Int -> a
getNth [] _ = error "Out of range"
getNth (h:t) 0 = h
getNth (h:t) n = getNth t (n-1)

这是使用
内置的操作员,例如
[1..10]!!5

要了解第一次尝试的结果,您需要了解 定义了列表数据。列表具有某种特殊的语法,但是 这样写吧

data List a = (:) a (List a)
            | []
因此,您的列表[1..10]实际上是按

(1 : (2 : (3 : (4 : []))))
此外,由于(:)操作符的正确关联性,您的模式 对于last1,它实际上看起来像

last1 :: [a] -> a
last1 (_:(x:[])) = x
这就是为什么“x”的类型与列表中的元素相同;这是第一次 (:)构造函数的参数

模式匹配允许您解构列表之类的数据结构,但是 需要知道他们必须这样做的“形状”。这就是为什么你不能直接 指定将提取列表最后一个元素的模式,因为 是列表可以具有的无限长度。这就是为什么 解决方案(last2)使用递归来解决问题。你知道什么图案吗 一个长度列表,以及在哪里找到最终元素;无论如何 否则,您可以丢弃第一个元素并提取最后一个元素 得到的较短的列表

如果您愿意,您可以添加更多的模式,但这并不能证明这一点 有帮助。你可以把它写成

last2 :: [a] -> a
last2 (x:[])     = x
last2 (_:x:[])   = x
last2 (_:_:x:[]) = x
        ...
last2 (x:xs) = last2 xs
但是如果没有无限多的案例,你永远无法完成这个函数 对于所有长度的输入列表。当你考虑到这个事实时,它更加可疑。
列表实际上可以无限长;您将使用什么模式来匹配它?

您确实可以使用
ViewPatterns
在列表的末尾进行模式匹配,因此让我们执行以下操作:

{-# LANGUAGE ViewPatterns #-}
在模式匹配之前,通过反转列表,重新定义您的
last1
last2
。 这使得它是O(n),但对于列表来说这是不可避免的

last1 (reverse -> (x:_)) = x
语法

main函数(viewFunction->pattern)=resultExpression

语法上的糖是什么

main function x=案例视图模式的函数x->resultExpression

所以你可以看到它实际上只是反转列表,然后模式匹配,但感觉更好。
viewFunction
是您喜欢的任何函数。 (扩展的目的之一是允许人们干净、方便地使用访问器功能 用于模式匹配,这样他们就不必在 在其上定义函数。)

如果列表为空,则此
last1
会给出一个错误,就像原始的
last
一样

*Main>last[]

***例外:前奏。最后:空列表

*Main>last1[]

***异常:Patterns.so.lhs:7:6-33:函数last1中的非穷举模式

好吧,不完全是这样,但我们可以
last1 (reverse -> (x:_)) = x
last1 _ = error "last1: empty list"
last2 (reverse -> (_:x:_)) = x
last2 _ = error "last2: list must have at least two elements"
maybeLast2 (reverse -> (_:x:_)) = Just x
maybeLast2 _ = Nothing
last4 (reverse -> (_:_:_:x:_)) = x