list - type - learn yourself a haskell
¿Tiene Haskell List Slices(es decir, Python)? (11)
¿Por qué no utilizar Data.Vector.slice
ya existente junto con Data.Vector.fromList
y Data.Vector.toList
(ver https://stackoverflow.com/a/8530351/9443841 )
import Data.Vector ( fromList, slice, toList )
import Data.Function ( (&) )
vSlice :: Int -> Int -> [a] -> [a]
vSlice start len xs =
xs
& fromList
& slice start len
& toList
¿Haskell tiene azúcar sintáctico similar a Python List Slices ?
Por ejemplo en Python:
x = [''a'',''b'',''c'',''d'']
x[1:3]
da los caracteres del índice 1 al índice 2 incluidos (o al índice 3 excluido):
[''b'',''c'']
Sé que Haskell tiene la función (!!)
para índices específicos, pero ¿existe una función de "división" o de rango de lista equivalente?
Cuando quiero emular un rango de Python (de m a n) en Haskell, utilizo una combinación de drop & take:
En Python:
print("Hello, World"[2:9]) # prints: "llo, Wo"
En Haskell:
print (drop 2 $ take 9 "Hello, World!") -- prints: "llo, Wo"
-- This is the same:
print (drop 2 (take 9 "Hello, World!")) -- prints: "llo, Wo"
Por supuesto, puede envolver esto en una función para que se comporte más como Python. Por ejemplo, si defines el !!! operador para ser:
(!!!) array (m, n) = drop m $ take n array
entonces podrás cortarlo como sigue:
"Hello, World!" !!! (2, 9) -- evaluates to "llo, Wo"
y usarlo en otra función como esta:
print $ "Hello, World!" !!! (2, 9) -- prints: "llo, Wo"
Espero que esto ayude, Jon W.
Las divisiones de Python también son compatibles con el paso:
>>> range(10)[::2]
[0, 2, 4, 6, 8]
>>> range(10)[2:8:2]
[2, 4, 6]
Tan inspirado por la caída de Dan Burton de cada elemento enésimo , implementé un corte con 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, Python también admite el inicio y la detención negativos (cuenta desde el final de la lista) y el paso negativo (invierte la lista, la detención se inicia 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 que comienzan <= k <stop (con positivos paso) o inicie> = k> detener (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 todavía funciona con listas infinitas dadas argumentos positivos, pero con paso negativo devuelve una lista vacía (teóricamente, aún podría devolver una sublista invertida) y con inicio o detención negativos ingresa en un ciclo infinito. Así que ten cuidado con los argumentos negativos.
No creo que uno esté incluido, pero podrías escribir uno bastante simple:
slice start end = take (end - start + 1) . drop start
Por supuesto, con la condición previa de que el start
y el end
están dentro de límites, y end >= start
.
No hay una función incorporada para cortar una lista, pero puede escribir una fácilmente usando drop
y take
:
slice from to xs = take (to - from + 1) (drop from xs)
Debe señalarse que, dado que las listas de Haskell son listas individualmente vinculadas (mientras que las listas de Python son matrices), la creación de sublistas como esa será O(to)
, no O(1)
como en python (asumiendo por supuesto que toda la lista realmente evaluado - de lo contrario, la pereza de Haskell tiene efecto).
Obviamente, mi versión foldl pierde contra el enfoque take-drop, pero tal vez alguien ve una manera de mejorarlo?
slice from to = reverse.snd.foldl build ((from, to + 1), []) where
build res@((_, 0), _) _ = res
build ((0, to), xs) x = ((0, to - 1), x:xs)
build ((from, to), xs) _ = ((from - 1, to - 1), xs)
Otra forma de hacerlo es con la función splitAt
from Data.List
: me parece un poco más fácil de leer y entender que usar take
y drop
, pero eso es solo una 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 debería ser equivalente a
let slice'' start stop xs = take (stop - start) $ drop start xs
que sin duda será más eficiente, pero que me resulta un poco más confuso que pensar en los índices donde la lista se divide en mitades anteriores y posteriores.
Si está tratando de hacer coincidir las "listas" de Python (que no es una lista, como otros señalan), entonces quizás quiera usar el paquete de vector Haskell, que sí tiene un slice integrado. Además, Vector
se puede evaluar en paralelo , lo que creo que es realmente genial.
Sin azúcar sintáctico En los casos en que se necesita, puede simplemente take
y drop
.
take 2 $ drop 1 $ "abcd" -- gives "bc"
Tuve un problema similar y utilicé 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 tan ordenado como las rebanadas de Python, pero cumple su función. Tenga en cuenta que indc puede estar en cualquier orden y no es necesario que sea contiguo.
Como se señaló, el uso de Haskell de listas LINKED hace que esta función sea O (n) donde n es el índice máximo al que se accede en comparación con el corte de Python, que depende de la cantidad de valores a los que se accede.
Descargo de responsabilidad: Todavía soy nuevo en Haskell y acepto cualquier corrección.
sublist start length = take length . snd . splitAt start
slice start end = snd .splitAt start . take end