Does Haskell have List Slices (i.e. Python)?

25,650

Solution 1

There's no built-in function to slice a list, but you can easily write one yourself using drop and take:

slice :: Int -> Int -> [a] -> [a]
slice from to xs = take (to - from + 1) (drop from xs)

It should be pointed out that since Haskell lists are singly linked lists (while python lists are arrays), creating sublists like that will be O(to), not O(to - from) like in python (assuming of course that the whole list actually gets evaluated - otherwise Haskell's laziness takes effect).

Solution 2

If you are trying to match Python "lists" (which isn't a list, as others note) then you might want to use the Haskell vector package which does have a built in slice. Also, Vector can be evaluated in parallel, which I think is really cool.

Solution 3

No syntactic sugar. In cases where it's needed, you can just take and drop.

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

Solution 4

I don't think one is included, but you could write one fairly simply:

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

Of course, with the precondition that start and end are in-bounds, and end >= start.

Solution 5

Python slices also support step:

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

So inspired by Dan Burton's dropping every Nth element I implemented a slice with step. It works on infinite lists!

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

However, Python also supports negative start and stop (it counts from end of list) and negative step (it reverses the list, stop becomes start and vice versa, and steps thru the list).

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

How do I implement that in Haskell? I need to reverse the list if the step is negative, start counting start and stop from the end of the list if these are negative, and keep in mind that the resulting list should contain elements with indexes start <= k < stop (with positive step) or start >= k > stop (with negative step).

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]

The algorithm still works with infinite lists given positive arguments, but with negative step it returns an empty list (theoretically, it still could return a reversed sublist) and with negative start or stop it enters an infinite loop. So be careful with negative arguments.

Share:
25,650
Jon W
Author by

Jon W

Probably a good developer

Updated on March 12, 2020

Comments

  • Jon W
    Jon W about 4 years

    Does Haskell have similar syntactic sugar to Python List Slices?

    For instance in Python:

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

    gives the characters from index 1 to index 2 included (or to index 3 excluded):

    ['b','c']
    

    I know Haskell has the (!!) function for specific indices, but is there an equivalent "slicing" or list range function?