Можете ли вы использовать сопоставление шаблонов для привязки последнего элемента списка?

Поскольку существует способ привязки головы и хвоста списка с помощью сопоставления с образцом, мне интересно, можно ли использовать сопоставление шаблонов для привязки последнего элемента списка?

Ответ 1

Да, вы можете, используя расширение 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) доступ к обоим концам.

Ответ 2

Вы можете использовать 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 - это просто любая функция, которая вам нравится. (Одна из целей расширения заключалась в том, чтобы позволить людям чисто и легко использовать функции доступа для сопоставления шаблонов, поэтому им не пришлось использовать базовую структуру своего типа данных, когда определяющие на нем функции.)

Ответ 3

Другие ответы объясняют решения, основанные на ViewPatterns. Если вы хотите сделать его более похожим на шаблон, вы можете упаковать его в PatternSynonym:

tailLast :: [a] -> Maybe ([a], a)
tailLast [email protected](_:_) = 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

Это мой первый день программирования Haskell, и я также столкнулся с той же проблемой, но я не мог решить использовать какой-то внешний артефакт, как это было предложено в предыдущих решениях.

Мое чувство о Haskell заключается в том, что если основной язык не имеет решения для вашей проблемы, тогда решение состоит в том, чтобы преобразовать вашу проблему, пока она не будет работать для языка.

В этом случае преобразование проблемы означает преобразование проблемы хвоста в задачу головы, которая кажется единственной поддерживаемой операцией в сопоставлении с образцом. Похоже, вы можете легко сделать это с помощью инверсии списка, а затем работать с перевернутым списком с помощью элементов главы, так как вы использовали бы хвостовые элементы в исходном списке и, наконец, при необходимости вернули результат в исходный порядок (например, if это был список).

Например, учитывая список целых чисел (например, [1,2,3,4,5,6]), предположим, что мы хотим построить этот список, в котором каждый второй элемент исходного списка, начиная с конца, заменено его двойным (упражнение взято из Homework1 это отличное введение в Haskell): [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.