Haskell的语法糖是否与Python List Slices相似?
例如在Python中:
x = ['a','b','c','d']
x[1:3]
包括从索引1到索引2的字符(或排除索引3):
['b','c']
我知道Haskell对特定索引具有(!!)
函数,但是是否存在等效的“切片”或列表范围函数?
答案 0 :(得分:52)
没有用于对列表进行切片的内置函数,但您可以使用drop
和take
轻松自行编写一个:
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)
没有语法糖。如果需要,您可以take
和drop
。
take 2 $ drop 1 $ "abcd" -- gives "bc"
答案 3 :(得分:9)
我认为不包括一个,但你可以简单地写一个:
slice start end = take (end - start + 1) . drop start
当然,前提是start
和end
是入界的,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
函数 - 我发现它比使用take
和drop
更容易阅读和理解 - 但这只是个人偏好:
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.fromList
和Data.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]