【问题标题】:Can you use pattern matching to bind the last element of a list?您可以使用模式匹配来绑定列表的最后一个元素吗?
【发布时间】:2011-09-27 23:42:13
【问题描述】:

既然有办法通过模式匹配来绑定列表的头部和尾部,我想知道你是否可以使用模式匹配来绑定列表的最后一个元素?

【问题讨论】:

    标签: haskell


    【解决方案1】:

    这是我使用 Haskell 编程的第一天,我也遇到了同样的问题,但我无法解决使用以前解决方案中建议的某种外部工件。

    我对 Haskell 的感觉是,如果核心语言无法解决您的问题,那么解决方案就是转换您的问题,直到它适用于该语言。

    在这种情况下,转换问题意味着将尾部问题转换为头部问题,这似乎是模式匹配中唯一支持的操作。事实证明,您可以使用列表反转轻松地做到这一点,然后使用头元素处理反向列表,就像在原始列表中使用尾元素一样,最后,如有必要,将结果恢复为初始顺序(例如,如果这是一个列表)。

    例如,给定一个整数列表(例如 [1,2,3,4,5,6]),假设我们要构建这个列表,其中从末尾开始的原始列表的每个第二个元素是替换为双精度(练习取自 this excellent introduction to HaskellHomework1):[2,2,6,4,10,6]。

    那么我们可以使用下面的:

    revert :: [Integer] -> [Integer]
    revert []     = []
    revert (x:[]) = [x]
    revert (x:xs) = (revert xs) ++ [x]
    
    doubleSecond :: [Integer] -> [Integer]
    doubleSecond []       = []
    doubleSecond (x:[])   = [x]
    doubleSecond (x:y:xs) = (x:2*y : (doubleSecond xs))
    
    doubleBeforeLast :: [Integer] -> [Integer]
    doubleBeforeLast l = ( revert (doubleSecond (revert l)) )
    
    main = putStrLn (show (doubleBeforeLast [1,2,3,4,5,6,7,8,9]))
    

    它显然比以前的解决方案要长得多,但对我来说感觉更像 Haskell-ish

    【讨论】:

    • 欢迎来到 StackOverflow,恭喜您有这么好的开始!反转列表以访问远端确实是一个非常 Haskellish 的解决方案(尽管它的效率相当低,但是如果您需要多次执行此操作,使用 last 可能会更糟......如果性能至关重要并且您需要访问最后一个元素,那么列表不是合适的类型)。
    • 谢谢。在我的菜鸟级别,我确实对发现 Haskell 编程习惯更感兴趣,而不是测量它们对性能的影响。我同意尝试以相反的顺序使用简单链表绝对是一个更糟糕的情况,我想这实际上是 Haskell 最初不提供尾部模式匹配的原因。 (Haskell 列表是使用简单链表实现的,对吧?)
    • 正确。 Haskell 列表非常适合作为堆栈或无限流,但它们对于随机访问来说确实很糟糕,访问远端并不比访问中间的任何元素更好。
    【解决方案2】:

    是的,您可以使用ViewPatterns 扩展名。

    Prelude> :set -XViewPatterns
    Prelude> let f (last -> x) = x*2
    Prelude> f [1, 2, 3]
    6
    

    但请注意,此模式总是会成功,因此您可能需要为列表为空的情况添加一个模式,否则last 将引发异常。

    Prelude> f []
    *** Exception: Prelude.last: empty list
    

    还要注意,这只是语法糖。与普通模式匹配不同,这是 O(n),因为您仍在访问单链表的最后一个元素。如果您需要更高效的访问,请考虑使用不同的数据结构,例如 Data.Sequence,它提供对两端的 O(1) 访问。

    【讨论】:

    • last 在空列表上引发异常。我希望摆脱最后,头,fst,snd。不能很好地处理空列表的函数。例如,模式匹配 let (x:xs) = "abcdefg" 就是我在问题中所指的内容。希望有一种类似的方法来获取最后一个元素,而不使用不安全的前奏函数。
    • @MichaelLitchard:例如,您可以定义一个返回MaybesafeLast 函数,然后您可以对其进行模式匹配,或者只是反转列表然后使用普通模式匹配.
    • 因为它总是成功,它根本不是一个“模式匹配”。 f = (*2) . last 更好。
    【解决方案3】:

    其他答案解释了基于ViewPatterns 的解决方案。如果你想让它更像模式匹配,你可以把它打包成一个PatternSynonym

    tailLast :: [a] -> Maybe ([a], a)
    tailLast xs@(_:_) = Just (init xs, last xs)
    tailLast _ = Nothing
    
    pattern Split x1 xs xn = x1 : (tailLast -> Just (xs, xn))
    

    然后将你的函数写成例如

    foo :: [a] -> (a, [a], a)
    foo (Split head mid last) = (head, mid, last)
    foo _ = error "foo: empty list"
    

    【讨论】:

      【解决方案4】:

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

      {-# LANGUAGE ViewPatterns #-}
      

      并使用reverse作为viewFunction,因为它总是成功,例如

      printLast :: Show a => IO ()
      printLast (reverse -> (x:_)) = print x
      printLast _ = putStrLn "Sorry, there wasn't a last element to print."
      

      这是安全的,因为只要您涵盖了所有可能性,它就不会抛出任何异常。 (例如,您可以重写它以返回 Maybe。)

      语法

      mainFunction (viewFunction -> pattern) = resultExpression

      是语法糖

      mainFunction x = case viewFunction x of pattern -> resultExpression

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        • 2023-04-02
        • 1970-01-01
        • 2020-03-17
        • 2011-06-14
        • 1970-01-01
        相关资源
        最近更新 更多