使用模式查找第n个元素

时间:2012-09-28 22:38:57

标签: haskell

我正在完成了解你一个Haskell ,以便快速掌握Haskell的基础知识。我对函数式编程和模式匹配都非常满意,但后者对于 Mathematica 的做法更为如此。

与第4.1章中head的幼稚实施一样,我继续执行last的天真实现:

last1 :: [a] -> a
last1 (_:x:[]) = x

但是,调用last1 [1,2,3,4]会导致错误Exception: ... Non-exhaustive patterns in function last1。我理解这个错误意味着指定的模式并不涵盖所有可能的输入,通常,需要一个包罗万象的模式(我没有提供)。但是,我不确定为什么我的输入会出现 this 错误。

问题1:我(我的方法不正确)的理解是第一个元素由_捕获,其余元素分配给x,这不是正是我的意图。但是,这不应该给出类型错误,因为我指定了[a] -> a,但x现在是一个列表?

请注意,关于如何编写工作last函数 - 我知道我可以将其编写为(以及其他可能性)

last2 :: [a] -> a
last2 [x] = x
last2 (_:x) = last2 x

问题2:沿着与Haskell中更好地理解模式匹配相同的主题,我如何使用模式匹配来挑选最后一个元素,或者更一般地来说,n元素来自给定的列表,比如[1..10]

This answer建议您可以使用与ViewPatterns扩展名的模式匹配来绑定最后一个元素,但似乎很奇怪,没有像head那样的类似“简单”模式

Mathematica 中,我可能会把它写成:

Range[10] /. {Repeated[_, {5}], x_, ___} :> x
(* 6 *)

选出第6个元素和

Range[10] /. {___, x_} :> x
(* 10 *)

选出非空列表的最后一个元素。

如果在本文后面有介绍,我道歉,但我正试图将每个主题和概念与我遇到的问题联系起来,以及我如何处理其他语言,以便我能够理解差异和相似性。

4 个答案:

答案 0 :(得分:8)

要弄清楚你第一次尝试的结果,你需要看看如何 列表数据已定义。列表享有一些特殊的语法,但你会 写下这样的东西。

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

但是如果没有无数个案例,你就永远无法完成这个功能 适用于所有长度的输入列表。当你考虑这个事实时,它更加可疑 列表实际上可以无限长;你会用什么模式来匹配呢?

答案 1 :(得分:2)

没有使用视图模式,模式匹配无法获得“last”元素。那是因为没有办法在不使用递归的情况下获取列表的最后一个元素(至少是隐式),而且,没有 decidable 方法来获取最后一个元素。

您的代码

last1 (_:x:[]) = x

应解析为

last1 (_:(x:[])) = x

可以脱糖成

last1 a = case a of
               (_:b) -> case b of
                             (x:c) -> case c of
                                           [] -> x

完成本练习后,我们会看到你的代码做了什么:你编写了一个匹配列表的模式,如果列表的最外层构造函数是一个缺点单元格,下一个构造函数是缺点,第三个构造函数是零。

所以在

的情况下
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

答案 2 :(得分:2)

你的例子

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

答案 3 :(得分:2)

您确实可以使用ViewPatterns在列表末尾进行模式匹配,所以让我们这样做:

{-# LANGUAGE ViewPatterns #-}

并在模式匹配之前通过撤消列表重新定义您的last1last2。 这使它成为 O(n),但这对列表来说是不可避免的。

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

语法

mainFunction (viewFunction -> pattern) = resultExpression

的语法糖

mainFunction x = case viewFunction x of pattern -> resultExpression

所以你可以看到它实际上只是反转列表然后模式匹配,但它感觉更好。 viewFunction只是你喜欢的任何功能。 (扩展的目的之一是允许人们干净利落地使用访问者功能 对于模式匹配,所以他们不必使用其数据类型的底层结构 在其上定义函数。)

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

*Main> last []
*** Exception: Prelude.last: empty list
*Main> last1 []
*** Exception: Patterns.so.lhs:7:6-33: Non-exhaustive patterns in function last1

嗯,好吧,不完全是,但我们可以通过添加

来改变它
last1 _ = error "last1: empty list"

给你

*Main> last1 []
*** Exception: last1: empty list

我们当然可以对last2使用相同的技巧:

last2 (reverse -> (_:x:_)) = x
last2 _ = error "last2: list must have at least two elements"

但定义

会更好
maybeLast2 (reverse -> (_:x:_)) = Just x
maybeLast2 _ = Nothing

您可以使用例如last4

继续这种方式
last4 (reverse -> (_:_:_:x:_)) = x

你可以看到使用reverse视图模式, 我们已经改变了(_:_:_:x:_)的语义 (ignore1st,ignore2nd,ignore3rd,get4th,ignoreTheRestOfTheList)(ignoreLast,ignore2ndLast,ignore3rdLast,get4thLast,ignoreTheRestOfTheList)

您注意到在Mathematica中,下划线的数量用于表示被忽略的元素数量。 在Haskell中,我们只使用一个_,但它可以用于任何忽略的值,并且存在 非对称列表构造函数:,语义取决于您所在的一侧,因此在a:b中,a必须表示 元素和b必须是一个列表(本身可能是c:d,因为:是正确的关联 - a:b:c表示 a:(b:c))。这就是为什么任何列表模式中的最后一个下划线重新表示ignoreTheRestOfTheList,并在 存在reverse视图函数,这意味着忽略列表的前面元素。

在Mathematica中隐藏的递归/回溯在这里使用viewFunction reverse(这是一个递归函数)是明确的。