Haskell有List List(即Python)吗?

时间:2011-01-04 19:59:07

标签: list haskell syntax

Haskell的语法糖是否与Python List Slices相似?

例如在Python中:

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

包括从索引1到索引2的字符(或排除索引3):

['b','c']

我知道Haskell对特定索引具有(!!)函数,但是是否存在等效的“切片”或列表范围函数?

12 个答案:

答案 0 :(得分:52)

没有用于对列表进行切片的内置函数,但您可以使用droptake轻松自行编写一个:

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

应该指出的是,由于Haskell列表是单链表(而python列表是数组),创建这样的子列表将是O(to),而不是像python中的O(1)(假设当然是实际上整个列表都会被评估 - 否则Haskell的懒惰就会生效。

答案 1 :(得分:34)

如果您正在尝试匹配Python“列表”(这不是列表,正如其他人所说),那么您可能希望使用内置vector的Haskell slice包。此外,Vector可以是evaluated in parallel,我认为这很酷。

答案 2 :(得分:15)

没有语法糖。如果需要,您可以takedrop

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

答案 3 :(得分:9)

我认为不包括一个,但你可以简单地写一个:

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

当然,前提是startend是入界的,end >= start

答案 4 :(得分:4)

Python切片也支持步骤:

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

受丹·伯顿的启发dropping every Nth element我实施了一步切片。它适用于无限列表!

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

但是,Python还支持负启动和停止(它从列表末尾开始计数)和负步骤(它反转列表,停止变为启动,反之亦然,并通过列表进行步骤)。

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

如何在Haskell中实现它?如果步骤为负,我需要反转列表,如果这些是否定的,则从列表末尾开始计数开始和停止,并记住结果列表应包含索引为 start< = k&lt的元素;停止(带正步骤)或开始> = k>停止(带负面步骤)。

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]

算法仍然适用于给定正参数的无限列表,但是使用负步骤它会返回一个空列表(理论上,它仍然可以返回反向子列表),并且负启动或停止它进入无限循环。所以要小心否定论点。

答案 5 :(得分:4)

我遇到了类似的问题并使用了列表理解:

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

也许不像python的切片一样整洁,但它确实起作用。注意,indc可以是任何顺序,不需要是连续的。

如上所述,Haskell使用LINKED列表使得函数O(n)其中n是最大索引被访问,而不是python的切片,这取决于值的数量访问。

免责声明:我还是Haskell的新手,我欢迎任何更正。

答案 6 :(得分:3)

另一种方法是使用splitAt中的Data.List函数 - 我发现它比使用takedrop更容易阅读和理解 - 但这只是个人偏好:

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

例如:

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

这应该相当于

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

这肯定会更有效率,但我觉得比考虑列表分为前后两半的索引更令人困惑。

答案 7 :(得分:2)

当我想在Haskell中模拟Python范围(从m到n)时,我使用drop&take的组合:

在Python中:

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

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

您当然可以将其包装在函数中,使其行为更像Python。例如,如果将 !!! 运算符定义为:

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

然后您就可以将其切成薄片:

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

并将其用于其他功能,例如:

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

我希望这会有所帮助,乔恩·W。

答案 8 :(得分:0)

显然,我的foldl版本在丢弃方式上失败了,但也许有人看到了改进它的方法?

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)

答案 9 :(得分:0)

sublist start length = take length . snd . splitAt start

slice start end = snd .splitAt start . take end

答案 10 :(得分:0)

为什么不将现有的 Data.Vector.slice Data.Vector.fromListData.Vector.toList一起使用(请参阅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 

答案 11 :(得分:0)

我已经写了这段代码,它也适用于负数,就像Python的列表切片一样,但反转列表除外,我通常发现这些列表没有意义或者与列表切片无关:

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

slice 0 x arr = 
  if x < 0 then slice 0 ((length arr)+(x)) arr
  else if x == (length arr) then arr
  else slice 0 (x) (init arr)

slice x y arr =
  if x < 0 then slice ((length arr)+x) y arr 
  else if y < 0 then slice x ((length arr)+y) arr
  else 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]