2011-09-27 14 views

Respuesta

13

Sí, puede hacerlo, utilizando la extensión ViewPatterns.

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

Tenga en cuenta que este patrón siempre tendrá éxito, sin embargo, por lo que probablemente querrá agregar un patrón para el caso en que la lista está vacía, de lo contrario last lanzará una excepción.

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

También tenga en cuenta que esto es solo azúcar sintáctica. A diferencia de la coincidencia de patrones normal, esto es O (n), ya que todavía está accediendo al último elemento de una lista de enlace único. Si necesita un acceso más eficiente, considere utilizar una estructura de datos diferente, como Data.Sequence, que ofrece O (1) acceso a ambos extremos.

+0

last arroja una excepción en una lista vacía. Estoy buscando alejarme del último, la cabeza, la primera y la última. Funciones que no manejan bien las listas vacías. La concordancia de patrones, let (x: xs) = "abcdefg" por ejemplo, es a lo que me refería en mi pregunta. Esperaba que hubiera un enfoque similar para obtener el último elemento, sin usar funciones de preludio inseguras. –

+2

@MichaelLitchard: Bueno, puede definir, por ejemplo, una función 'safeLast' que devuelve' Maybe', con la que puede coincidir el patrón, o tal vez simplemente invertir la lista y luego usar una coincidencia de patrón normal. – hammar

+2

, ya que siempre tiene éxito no es para nada una "coincidencia de patrón". 'f = (* 2). last' es mejor. – u0b34a0f6ae

11

Puede utilizar ViewPatterns que ver la coincidencia de patrones al final de una lista, por lo que vamos a hacer

{-# LANGUAGE ViewPatterns #-} 

y utilizar reverse como el viewFunction, porque siempre tiene éxito, así que por ejemplo

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

Esto es seguro en el sentido de que no arroja ninguna excepción siempre que cubras todas las posibilidades. (Se puede escribir de nuevo para devolver un Maybe, por ejemplo.)

La sintaxis

mainFunction (viewFunction -> pattern) = resultExpression

es el azúcar sintáctica para

mainFunction x = case viewFunction x of pattern -> resultExpression

para que pueda ver que en realidad sólo invierte la lista y el patrón coincide con eso, pero se siente mejor. viewFunction es cualquier función que desee. (Uno de los objetivos de la extensión fue permitir a la gente a utilizar de manera limpia y fácil de acceso para funciones coincidencia de patrones para que no tuvieran que utilizar la estructura subyacente de su tipo de datos cuando definir funciones en él.)

4

Las otras respuestas explican las soluciones basadas en ViewPatterns. Si desea hacerlo más coincidencia de patrones similares, puede empaquetar que en un 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)) 

y luego escriba su función como por ejemplo,

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

Este es mi primer día de programación Haskell y también me encontré con el mismo problema, pero no pudo resolver a utilizar algún tipo de artefacto externo como se sugiere en las soluciones anteriores.

Mi sensación sobre Haskell es que si el lenguaje central no tiene solución para su problema, entonces la solución es transformar su problema hasta que funcione para el idioma.

En este caso, transformar el problema significa transformar un problema de cola en un problema de cabeza, que parece ser la única operación compatible en la coincidencia de patrones. Resulta que puede hacer eso fácilmente usando una inversión de lista, luego trabajar en la lista invertida usando elementos de encabezado ya que usaría elementos de cola en la lista original, y finalmente, si es necesario, revertir el resultado al orden inicial (por ejemplo, si era una lista).

Por ejemplo, dada una lista de enteros (por ejemplo [1,2,3,4,5,6]), supongamos que queremos construir esta lista en la que cada segundo elemento de la lista original comienza desde el final es reemplazado por su doble (ejercicio tomado de Homework1 de this excellent introduction to Haskell): [2,2,6,4,10,6].

entonces podemos utilizar el siguiente:

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])) 

Es obvio que es mucho más larga que las soluciones anteriores, pero se siente más Haskell-ish a mí.

+1

¡Bienvenido a StackOverflow, y felicitaciones por tan buen comienzo! Invertir la lista para acceder al otro extremo es de hecho una solución muy Haskellish (aunque es bastante ineficiente, pero usar 'last' puede ser incluso peor si necesita hacerlo varias veces ... si el rendimiento es crítico y necesita acceder un último elemento, luego las listas no son un tipo adecuado). – leftaroundabout

+0

Gracias. En mi nivel de novato, estoy realmente más interesado en descubrir los modismos de programación de Haskell que en medir su impacto en el rendimiento. Estoy de acuerdo en que definitivamente es un caso peor tratar de usar una lista de enlaces simples en orden inverso y supongo que esta es la razón por la que Haskell no ofrece coincidencia de patrones de cola al principio. (Las listas de Haskell se implementan usando listas de enlaces simples, ¿no?) – OlivierD

+1

Correcto. Las listas de Haskell son geniales como stacks, o como streams infinitos, pero son realmente malas para el acceso aleatorio, y acceder al otro extremo no es mejor que acceder a cualquier elemento en el medio. – leftaroundabout

Cuestiones relacionadas