2011-01-04 16 views
31

¿Tiene Haskell azúcar sintáctico similar a Python List Slices?¿Tiene Haskell List Slices (es decir, Python)?

Por ejemplo, en Python:

x = ['a','b','c','d'] 
x[1:3] 

da los caracteres de índice 1 al índice 2 incluidos (o al índice 3 excluido):

['b','c'] 

sé Haskell tiene la función (!!) para específica índices, pero ¿hay una función de "división" o de rango de lista equivalente?

Respuesta

45

No hay ninguna función integrada para cortar una lista, pero se puede escribir fácilmente uno por sí mismo utilizando drop y take:

slice from to xs = take (to - from + 1) (drop from xs) 

Cabe señalar que, dado que las listas de Haskell están vinculados por separado las listas (mientras que las listas de Python son matrices), la creación de sublistas como esa será O(to), no O(1) como en python (suponiendo, por supuesto, que toda la lista en realidad sea evaluada; de lo contrario, la pereza de Haskell se aplica).

+6

Si 'slice 1 2 ['a', 'b', 'c', 'd']' es muy prolijo para usted, también puede agregar su propio azúcar 'xs! @ (From, to) = slice ft xs', para que pueda hacer '['a', 'b', 'c', 'd']! @ (1,2)' – rampion

+15

@rampion: Aquí hay otro abuso de diversión de operadores de infijo: '(!>) = drop', '( ['A' .. 'z']

+5

Me gusta '' map ("abcd" !!) [1 .. 2] '' (para obtener "bc"), aunque es terriblemente ineficiente (cuadrático). ("Me gusta" en el sentido "lindo", debido a la cuadrática, nunca lo usaría en la práctica). – jon

7

no creo que uno ha sido comprendido, pero se podría escribir una manera bastante simple:

slice start end = take (end - start + 1) . drop start 

Por supuesto, con la condición de que start y end están en la cancha, y end >= start.

16

Sin azúcar sintáctica. En los casos en que sea necesario, puede simplemente take y drop.

take 2 $ drop 1 $ "abcd" -- gives "bc" 
32

si usted está tratando de igualar "listas" de Python (que no es una lista, como otros nota), entonces es posible que desee utilizar el paquete de vector Haskell que sí tiene incorporado un slice. Además, Vector puede ser evaluated in parallel, lo que creo que es genial.

0

Obviamente, mi versión plegable pierde contra el enfoque take-drop, pero tal vez alguien ve la manera de mejorarla?

slice from to = reverse.snd.foldl build ((from, to + 1), []) where 
    build [email protected]((_, 0), _) _ = res 
    build ((0, to), xs) x = ((0, to - 1), x:xs) 
    build ((from, to), xs) _ = ((from - 1, to - 1), xs) 
3

Otra forma de hacerlo es con la función de splitAtData.List - Me parece que hace que sea un poco más fácil de leer y entender que el uso de take y drop - pero eso es simplemente preferencia personal:

import Data.List 
slice :: Int -> Int -> [a] -> [a] 
slice start stop xs = fst $ splitAt (stop - start) (snd $ splitAt start xs) 

Por ejemplo:

Prelude Data.List> slice 0 2 [1, 2, 3, 4, 5, 6] 
[1,2] 
Prelude Data.List> slice 0 0 [1, 2, 3, 4, 5, 6] 
[] 
Prelude Data.List> slice 5 2 [1, 2, 3, 4, 5, 6] 
[] 
Prelude Data.List> slice 1 4 [1, 2, 3, 4, 5, 6] 
[2,3,4] 
Prelude Data.List> slice 5 7 [1, 2, 3, 4, 5, 6] 
[6] 
Prelude Data.List> slice 6 10 [1, 2, 3, 4, 5, 6] 
[] 

Esto debe ser equivalente a

let slice' start stop xs = take (stop - start) $ drop start xs 

que sin duda será más eficiente, pero que me parece un poco más confuso que pensar en los índices donde la lista se divide en mitades anteriores y posteriores.

4

rebanadas de Python también apoyan paso:

>>> range(10)[::2] 
[0, 2, 4, 6, 8] 
>>> range(10)[2:8:2] 
[2, 4, 6] 

Así inspirado por Dan Burton dropping every Nth element he implementado una rebanada con el paso. ¡Funciona en listas infinitas!

takeStep :: Int -> [a] -> [a] 
takeStep _ [] = [] 
takeStep n (x:xs) = x : takeStep n (drop (n-1) xs) 

slice :: Int -> Int -> Int -> [a] -> [a] 
slice start stop step = takeStep step . take (stop - start) . drop start 

Sin embargo, también es compatible con Python inicio negativo y se detiene (se cuenta desde el final de la lista) y la etapa negativa (se invierte la lista, se convierte en parada iniciar y viceversa, y los pasos a través de la lista).

from pprint import pprint # enter all of this into Python interpreter 
pprint([range(10)[ 2: 6],  # [2, 3, 4, 5] 
     range(10)[ 6: 2:-1], # [6, 5, 4, 3] 
     range(10)[ 6: 2:-2], # [6, 4]  
     range(10)[-8: 6],  # [2, 3, 4, 5] 
     range(10)[ 2:-4],  # [2, 3, 4, 5] 
     range(10)[-8:-4],  # [2, 3, 4, 5] 
     range(10)[ 6:-8:-1], # [6, 5, 4, 3] 
     range(10)[-4: 2:-1], # [6, 5, 4, 3] 
     range(10)[-4:-8:-1]]) # [6, 5, 4, 3]] 

¿Cómo implemento eso en Haskell? Necesito revertir la lista si el paso es negativo, empiezo a contar desde el final de la lista si estos son negativos, y tenga en cuenta que la lista resultante debe contener elementos con índices start < = k < stop (con paso positivo) o start> = k> stop (con paso negativo).

takeStep :: Int -> [a] -> [a] 
takeStep _ [] = [] 
takeStep n (x:xs) 
    | n >= 0 = x : takeStep n (drop (n-1) xs) 
    | otherwise = takeStep (-n) (reverse xs) 

slice :: Int -> Int -> Int -> [a] -> [a] 
slice a e d xs = z . y . x $ xs -- a:start, e:stop, d:step 
    where a' = if a >= 0 then a else (length xs + a) 
     e' = if e >= 0 then e else (length xs + e) 
     x = if d >= 0 then drop a' else drop e' 
     y = if d >= 0 then take (e'-a') else take (a'-e'+1) 
     z = takeStep d 

test :: IO() -- slice works exactly in both languages 
test = forM_ t (putStrLn . show) 
    where xs = [0..9] 
     t = [slice 2 6 1 xs, -- [2, 3, 4, 5] 
      slice 6 2 (-1) xs, -- [6, 5, 4, 3] 
      slice 6 2 (-2) xs, -- [6, 4] 
      slice (-8) 6 1 xs, -- [2, 3, 4, 5] 
      slice 2 (-4) 1 xs, -- [2, 3, 4, 5] 
      slice (-8)(-4) 1 xs, -- [2, 3, 4, 5] 
      slice 6 (-8)(-1) xs, -- [6, 5, 4, 3] 
      slice (-4) 2 (-1) xs, -- [6, 5, 4, 3] 
      slice (-4)(-8)(-1) xs] -- [6, 5, 4, 3] 

El algoritmo sigue funcionando con infinitas listas dadas argumentos positivos, pero con el paso negativo que devuelve una lista vacía (en teoría, todavía podría devolver una lista secundaria invertida) y con arranque negativo o detener entra en un bucle infinito. Así que ten cuidado con los argumentos negativos.

+1

¿Qué hay de la incorporación de rebanadas multidimensionales? 'array [:,:, 0]' obtiene para todas las filas, para todas las columnas, obtiene el 0 ° elemento y recupera una matriz que contiene filas que contienen el 0 ° elemento ... – CMCDragonkai

0
sublist start length = take length . snd . splitAt start 

slice start end = snd .splitAt start . take end 
3

tuve un problema similar y se utiliza una lista de comprensión:

-- Where lst is an arbitrary list and indc is a list of indices 

[lst!!x|x<-[1..]] -- all of lst 
[lst!!x|x<-[1,3..]] -- odd-indexed elements of lst 
[lst!!x|x<-indc] 

Tal vez no sea tan ordenada como rebanadas de pitón, pero hace el trabajo. Tenga en cuenta que indc puede estar en cualquier orden y no es necesario que sea contiguo.

Como se ha indicado, el uso de Haskell de listas enlazadas hace esta función O (n) donde n es el índice máximo visitada en lugar de corte en rodajas de pitón que depende del número de valores accedido.

Descargo de responsabilidad: Todavía soy nuevo en Haskell y acepto cualquier corrección.

+0

No estoy seguro de cómo la evaluación perezosa de Haskell afectaría a este , en todo caso, pero creo que si está accediendo a elementos en orden inverso, puede ser O (n^2), ya que las listas de Haskell están unidas individualmente; por lo que para cada elemento, necesitarás recorrer toda la lista hasta ese elemento. –