Haskell 是否有与 Python List Slices 类似的语法糖?
以 Python 为例:
x = ['a','b','c','d']
x[1:3]
给出包含索引 1 到索引 2 的字符(或排除索引 3):
['b','c']
我知道 Haskell 有针对特定索引的
(!!)
函数,但是是否有等效的“切片”或列表范围函数?
没有内置函数来对列表进行切片,但您可以使用
drop
和 take
轻松地自己编写一个函数:
slice :: Int -> Int -> [a] -> [a]
slice from to xs = take (to - from + 1) (drop from xs)
需要指出的是,由于 Haskell 列表是单链表(而 Python 列表是数组),因此创建这样的子列表将是
O(to)
,而不是像 Python 中那样 O(to - from)
(当然假设整个列表实际上都被评估) - 否则 Haskell 的懒惰就会生效)。
没有语法糖。如果需要,您可以只使用
take
和 drop
。
take 2 $ drop 1 $ "abcd" -- gives "bc"
我认为其中没有包含其中,但你可以相当简单地写一个:
slice start end = take (end - start + 1) . drop start
当然,前提是
start
和end
都在界内,并且end >= start
。
Python 切片还支持步骤:
>>> range(10)[::2]
[0, 2, 4, 6, 8]
>>> range(10)[2:8:2]
[2, 4, 6]
受到 Dan Burton 的删除每个第 N 个元素的启发,我用步骤实现了一个切片。它适用于无限列表!
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 < stop (具有正步骤)或start >= k > stop(负步长)。
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]
该算法仍然适用于给定正参数的无限列表,但如果使用负步骤,它会返回一个空列表(理论上,它仍然可以返回一个反向子列表),如果使用负开始或停止,它会进入无限循环。所以要小心负面的论据。
我遇到了类似的问题并使用了列表理解:
-- 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 还很陌生,欢迎任何更正。
当我想在 Haskell 中模拟 Python 范围(从 m 到 n)时,我使用 drop 和 take 的组合:
print("Hello, World"[2:9]) # prints: "llo, Wo"
在哈斯克尔:
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。
另一种方法是使用
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
这肯定会更有效,但我发现这比考虑列表分为前半部分和后半部分的索引更令人困惑。
为什么不将现有的
与 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
我编写的代码也适用于负数,就像 Python 的列表切片一样,但反转列表除外,我发现这与列表切片无关:
显然我的foldl版本输给了take-drop方法,但也许有人找到了改进它的方法?
sublist start length = take length . snd . splitAt start
slice start end = snd .splitAt start . take end
类似于列表切片的事情可以通过生成器通过循环数组索引来非常简单地完成
<- [0..(length sp1 - 1)], (el >