在Haskell中获取子列表

24

这可能是一个简单的问题,但我已经查阅文档并在谷歌上搜索了示例,仍然不确定答案。

如果我有像这样的列表:

[1,2,3,4,5,6,7,8,9,0]

我想要提取一个切片,比如从索引4到索引8,也就是说我想要:

[5,6,7,8,9]

在 Haskell 中,哪种方式是惯用的?


可能是Does Haskell have List Slices (i.e. Python)?的重复问题。 - Mirzhan Irkegulov
4个回答

40

首先,那不是一个数组,而是一个列表。我并不是(仅仅)吹毛求疵,因为在Haskell中,数组比列表更加棘手。

话虽如此,一种常见的方法是同时使用takedrop

Prelude> drop 4 . take 9 $ [1,2,3,4,5,6,7,8,9,0]
[5,6,7,8,9]
Prelude> take (9-4) . drop 4 $ [1,2,3,4,5,6,7,8,9,0]
[5,6,7,8,9]
后者更加高效。

有问题吗?它们不应该被用于所有情况,标准的数组模块也不是很好用,但是当适用时,vector非常有用。虽然我同意在尝试使用它们之前应该掌握列表的熟练程度... - ehird
1
@ehird:你列出了我称它们为有问题的主要原因。但我并没有称它们为“没用”和“有害”的 :-) - ibid
3
概括地说:slice begin end = take (end - begin) . drop begin - 我相当确信 GHC 可以优化掉第一种实现的低效性。 - Dan Burton
1
是的,我非常确定效率评论是不准确的;即使使用简单的非严格评估,您也永远不会过多地查看列表,因此除非GHC 悲观地对待它(我怀疑),否则第一个更清晰的版本应该没问题。 - ehird
3
从天真的角度看,第一个版本的效率会更低,因为每个被丢弃的值都需要从take延迟计算中得出(额外的比较和递增)。而第二个版本会一次性将所有的丢弃操作完成,所以 take 操作的是普通列表。如果这是性能关键点,则显然需要进行性能分析。可以这样考虑:为了获取第一个值,第一个版本需要从take中获取5个元素,而第二个版本仅获取一个元素。两个版本在drop和计算列表方面都做了相同数量的工作。 - Philip JF
显示剩余3条评论

9
您可能会对Data.Vector (slice)感兴趣。
ghci> import Data.Vector
ghci> let v = fromList [1..10]
ghci> v
fromList [1,2,3,4,5,6,7,8,9,10]
ghci> slice 4 5 v
fromList [5,6,7,8,9]

请注意,在 Data.Vector 中,slice 函数的输入参数是切片的起始索引和长度。

6
> drop 4 (take 9 [1,2,3,4,5,6,7,8,9,0])

[5,6,7,8,9]

1

嗯,不是很实用,但或许可以改进?

(\(x,y) -> if 4 <= y && y <= 9 then [x] else []) =<< zip [1,2,3,4,5,6,7,8,9] [0..]

map snd . filter (liftA2 (&&) (>= 4) (< 9) . fst) . zip [0..] 怎么样(注意 (< 9) 以匹配问题的行为)? - ehird
装饰-处理-取消装饰的方法似乎对于这个问题来说需要额外的工作量。 - Dan Burton
也许库中应该有类似 indexedFilter f = map snd . filter (f.fst) . zip [0..] 的东西,或者更一般化的形式? - Landei
@Landei:对我来说,它的用途太有限了。通常有其他方法可以解决问题,而不必诉诸于压缩索引。 - ehird

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接