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

问题描述 投票:0回答:13

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

以 Python 为例:

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

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

['b','c']

我知道 Haskell 有针对特定索引的

(!!)
函数,但是是否有等效的“切片”或列表范围函数?

list haskell syntax
13个回答
62
投票

没有内置函数来对列表进行切片,但您可以使用

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


35
投票

如果您尝试匹配 Python“列表”(这不是一个列表,正如其他人指出的那样),那么您可能需要使用 Haskell vector 包,它有一个内置的 slice。另外,

Vector
可以并行评估,我认为这真的很酷。


19
投票

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

take
drop

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

12
投票

我认为其中没有包含其中,但你可以相当简单地写一个:

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

当然,前提是

start
end
都在界内,并且
end >= start


6
投票

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]

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


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),其中 n 是访问的“最大索引”,而不是 Python 的切片,后者取决于访问的“值的数量”。 免责声明:我对 Haskell 还很陌生,欢迎任何更正。

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


4
投票

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

3
投票
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

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


为什么不将现有的


1
投票

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 的列表切片一样,但反转列表除外,我发现这与列表切片无关:

1
投票

显然我的foldl版本输给了take-drop方法,但也许有人找到了改进它的方法?

0
投票

sublist start length = take length . snd . splitAt start

slice start end = snd .splitAt start . take end

0
投票
类似于列表切片的事情可以通过生成器通过循环数组索引来非常简单地完成

0
投票
= (ind1-1) && el

<- [0..(length sp1 - 1)], (el >

© www.soinside.com 2019 - 2024. All rights reserved.