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

ListHaskellSyntax

List Problem Overview


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?

List Solutions


Solution 1 - List

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

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

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

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

Solution 4 - List

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

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.

Solution 6 - List

I had a similar problem and used a list comprehension:

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

Perhaps not as tidy as python's slices, but it does the job. Note that indc can be in any order an need not be contiguous.

As noted, Haskell's use of LINKED lists makes this function O(n) where n is the maximum index accessed as opposed to python's slicing which depends on the number of values accessed.

Disclaimer: I am still new to Haskell and I welcome any corrections.

Solution 7 - List

Another way to do this is with the function splitAt from Data.List -- I find it makes it a little easier to read and understand than using take and drop -- but that's just personal preference:

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

For example:

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

This should be equivalent to

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

which will certainly be more efficient, but which I find a little more confusing than thinking about the indices where the list is split into front and back halves.

Solution 8 - List

When I want to emulate a Python range (from m to n) in Haskell, I use a combination of drop & take:

In Python:

print("Hello, World"[2:9])  # prints:  "llo, Wo"

In 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"

You can, of course, wrap this in a function to make it behave more like Python. For example, if you define the !!! operator to be:

(!!!) array (m, n) = drop m $ take n array

then you will be able to slice it up like:

"Hello, World!" !!! (2, 9)  -- evaluates to "llo, Wo"

and use it in another function like this:

print $ "Hello, World!" !!! (2, 9)  -- prints:  "llo, Wo"

I hope this helps, Jon W.

Solution 9 - List

Why not use already existing Data.Vector.slice together with Data.Vector.fromList and Data.Vector.toList (see 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 

Solution 10 - List

I've wrote this code that works for negative numbers as well, like Python's list slicing, except for reversing lists, which I find unrelated to list slicing:

slice :: Int -> Int -> [a] -> [a]

slice 0 x arr
  | x < 0 = slice 0 ((length arr)+(x)) arr
  | x == (length arr) = arr
  | otherwise = slice 0 (x) (init arr)

slice x y arr
  | x < 0 = slice ((length arr)+x) y arr 
  | y < 0 = slice x ((length arr)+y) arr
  | otherwise = slice (x-1) (y-1) (tail arr)

main = do
  print(slice (-3) (-1) [3, 4, 29, 4, 6]) -- [29,4]
  print(slice (2) (-1) [35, 345, 23, 24, 69, 2, 34, 523]) -- [23,24,69,32,34]
  print(slice 2 5 [34, 5, 5, 3, 43, 4, 23] ) -- [5,3,43]


Solution 11 - List

Obviously my foldl version loses against the take-drop approach, but maybe someone sees a way to improve it?

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)

Solution 12 - List

sublist start length = take length . snd . splitAt start

slice start end = snd .splitAt start . take end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJon WView Question on Stackoverflow
Solution 1 - Listsepp2kView Answer on Stackoverflow
Solution 2 - ListThomas M. DuBuissonView Answer on Stackoverflow
Solution 3 - ListChuckView Answer on Stackoverflow
Solution 4 - ListScottWestView Answer on Stackoverflow
Solution 5 - ListMirzhan IrkegulovView Answer on Stackoverflow
Solution 6 - ListEzryView Answer on Stackoverflow
Solution 7 - ListelyView Answer on Stackoverflow
Solution 8 - ListJ-LView Answer on Stackoverflow
Solution 9 - ListLo HaBuyshanView Answer on Stackoverflow
Solution 10 - ListSapphire_BrickView Answer on Stackoverflow
Solution 11 - ListLandeiView Answer on Stackoverflow
Solution 12 - ListMeowView Answer on Stackoverflow