Haskell 有列表切片(即 Python)吗?

Posted

技术标签:

【中文标题】Haskell 有列表切片(即 Python)吗?【英文标题】:Does Haskell have List Slices (i.e. Python)? 【发布时间】:2011-06-03 15:01:30 【问题描述】:

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

例如在 Python 中:

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

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

['b','c']

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

【问题讨论】:

【参考方案1】:

我编写的这段代码也适用于负数,比如 Python 的列表切片,除了反转列表,我发现它与列表切片无关:

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]


【讨论】:

为什么不使用警卫? @Elmex80s 它们基本上是可以互换的;见***.com/questions/9345589/… 如果你坚持,但我认为 if-else_if-else 语句对于 Haskell 业余爱好者来说更容易理解【参考方案2】:

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

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 的惰性会生效)。

【讨论】:

如果slice 1 2 ['a','b','c','d']对你来说太罗嗦了,你也可以自己加糖xs !@ (from,to) = slice f t xs,这样['a','b','c','d'] !@ (1,2)就可以了 @rampion:这是对中缀运算符的另一个有趣滥用:(!&gt;) = drop(&lt;!) = flip take,确保后者具有更高的固定性。现在您可以像这样对列表进行切片:2 !&gt; ['a'..'z'] &lt;! 5 = "cde"。不过,这变得很愚蠢。 我喜欢map ("abcd" !!) [1 .. 2](得到“bc”),尽管它的效率非常低(二次)。 (“可爱”意义上的“喜欢”;由于是二次方,我永远不会在实践中使用它。) > 创建这样的子列表将是 O(to),而不是像 python 中的 O(1)。在 python 中创建切片运行在 O(k) wiki.python.org/moin/TimeComplexity Python 切片不包含结束索引,因此slice from to xs = take (to - from ) (drop from xs) 更相似。在索引 3 中的问题 d 被排除在外。【参考方案3】:

当我想在 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"

我希望这会有所帮助,Jon W。

【讨论】:

【参考方案4】:

为什么不将现有的Data.Vector.sliceData.Vector.fromListData.Vector.toList一起使用(参见https://***.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 

【讨论】:

【参考方案5】:

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

-- 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) 成为 O(n),其中 n 是 最大索引 访问而不是 python 的切片,它取决于 值的数量 访问。

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

【讨论】:

我不确定 Haskell 的惰性评估会如何影响这一点,如果有的话,但我认为如果你以相反的顺序访问元素,它可能是 O(n^2),因为Haskell 的列表是单链接的;所以对于每个元素,你需要遍历整个列表直到那个元素。【参考方案6】:
sublist start length = take length . snd . splitAt start

slice start end = snd .splitAt start . take end

【讨论】:

【参考方案7】:

Python 切片也支持 step:

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

受到 Dan Burton 的 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 > 停止(带负步)。

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]

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

【讨论】:

还加入多维切片怎么样? array[:,:,0] 获取所有行,所有列,获取第 0 个元素并返回一个包含第 0 个元素的行的数组...【参考方案8】:

另一种方法是使用来自Data.List 的函数splitAt——我发现它比使用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

这肯定会更有效,但我发现这比考虑将列表分为前半部分和后半部分的索引更令人困惑。

【讨论】:

【参考方案9】:

显然我的 foldl 版本在 take-drop 方法中失败了,但也许有人看到了改进它的方法?

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)

【讨论】:

【参考方案10】:

如果您尝试匹配 Python“列表”(这不是列表,正如其他人所指出的那样),那么您可能想要使用 Haskell vector 包,它确实有一个内置的 slice。另外,Vector 可以是evaluated in parallel,我觉得这很酷。

【讨论】:

【参考方案11】:

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

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

【讨论】:

【参考方案12】:

我不认为其中包含一个,但您可以相当简单地编写一个:

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

当然,前提是startend 是在界内,而end &gt;= start

【讨论】:

以上是关于Haskell 有列表切片(即 Python)吗?的主要内容,如果未能解决你的问题,请参考以下文章

Haskell 有像 Python 和 Ruby 这样的 splat 运算符吗?

我可以在 Python 列表上创建“视图”吗?

Python 等效于 Haskell 的 [1..](索引列表)

关于python列表(list)切片[start:stop:step]的理解

Python中列表/字符串切片slice?

如何展平haskell中的列表列表