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