使用fold检查列表Haskell中的连续True

使用fold检查列表Haskell中的连续True,haskell,Haskell,我是哈斯克尔的新手 对于项目Euler问题,我生成了一个布尔列表 [True, False, False, True, False. . .True] 我想写一个函数,找到前四个连续的True,然后在列表中返回它们的索引 直觉上,我知道我应该通过折叠和模式匹配来实现这一点。你能帮我吗?像这样有褶皱的东西?(我不知道如何检索元素的索引。) 谢谢你的帮助 最简单的方法是拉上拉链,然后用特制的折页。首先查看每组四个连续元素,看看它们是否都是真的: import Data.List (zipWith4

我是哈斯克尔的新手

对于项目Euler问题,我生成了一个布尔列表

[True, False, False, True, False. . .True]
我想写一个函数,找到前四个连续的True,然后在列表中返回它们的索引

直觉上,我知道我应该通过折叠和模式匹配来实现这一点。你能帮我吗?像这样有褶皱的东西?(我不知道如何检索元素的索引。)


谢谢你的帮助

最简单的方法是拉上拉链,然后用特制的折页。首先查看每组四个连续元素,看看它们是否都是真的:

import Data.List (zipWith4, findIndex)

consec4 :: [Bool] -> [Bool]
consec4 xs = zipWith4 (\x y z w -> x && y && z && w) xs (drop 1 xs) (drop 2 xs) (drop 3 xs)
现在你只需要

fstConsec4 :: [Bool] -> Maybe Int
fstConsec4 = findIndex id . consec4
现在这不太可能是你能做的最快的,而且它不能很好地推广到更大的窗口

相反,我们可以更直接地跳进褶皱中,采取不同的方法。请注意,此特定版本在GHC 7.10或更高版本中的表现可能比在早期版本中更好。为了清晰起见,我使用了bang模式,但您可以使用
seq
$可移植性,如果您愿意

让我们首先定义一个在
数据中明显缺失的函数。List

{-# INLINE foldrWithIndex #-}
foldrWithIndex :: (Int -> a -> b -> b) -> b -> [a] -> b
foldrWithIndex c n xs = foldr go (`seq` n) xs 0
  where
    go x r !i = c i x (r $ i + 1)
consec :: Int -> [Bool] -> [Int]
consec n = findIndices (isPrefixOf (replicate n True)) . tails
使用此函数,我们可以轻松定义一个函数,用于查找第一组
n
连续
True
值的开头索引

consec :: Int -> [Bool] -> Maybe Int
consec n xs = foldrWithIndex go (`seq` Nothing) xs n
  where
    go _ix False r !_ = r n
    go ix True _r 1 = Just (ix - n + 1)
    go _ix True r need = r (need - 1)
快速查看一下它产生的GHC核心表明它可能非常有效(这是使用
GHC-O2-ddump simpl-dsuppress all-dno suppress类型签名产生的):

极端性能黑客攻击 从理论上讲,应该可以做得更好,特别是在查看长列表时。特别是,如果
True
False
值都很频繁,则
Bool
值上的分支可能会导致许多错误预测的分支。在这种情况下,最好将值放入一个小的位向量中,然后对其进行一些小技巧。不幸的是,要说服GHC做这类事情可能很难,但我可能会稍后再试。

tldr

findIndices(\x->(x==True))$map(\x->all(=True)x)$sliding 2 bs

定义一个与Scala中类似的
滑动
函数

sliding :: Int -> [a] -> [[a]]
sliding size [] = []
sliding size ls@(x:xs) = if length ls >= size then (take size ls) : sliding size xs else sliding size xs
使用
滑动2
[真、假、假、真、真、假、真、假、真、假、真、真、真]
给出

[[True,False],[False,False],[False,True],[True,True],[True,False],[False,True],[True,False],[False,True],[True,True]

然后我们可以
map
查看每个子列表是否包含
all
True
值,并获取与
findIndices
相关的索引

findIndices(\x->(x==True))$map(\x->all(=True)x)$sliding 2 bs


[3,8]

这里是另一个解决方案:

consec :: Int -> [Bool] -> [Int]
consec n = findIndices and . foldr (zipWith (:)) (repeat []) . take n . tails

这是基于其他答案的想法<代码>取n。tails
返回一个矩阵,该矩阵包含长度为
的连续元素列表是否使用了
折叠
要求? 因为除此之外,它可以直接用
Data.List中的标准函数表示:

{-# INLINE foldrWithIndex #-}
foldrWithIndex :: (Int -> a -> b -> b) -> b -> [a] -> b
foldrWithIndex c n xs = foldr go (`seq` n) xs 0
  where
    go x r !i = c i x (r $ i + 1)
consec :: Int -> [Bool] -> [Int]
consec n = findIndices (isPrefixOf (replicate n True)) . tails

只要把它读成英文:
findIndices
的所有
trail
replicate
-ed
n
times
True
也许我们应该简单地计算连续的
True
s,而不是把它们列成子列表

consec :: Int -> [Bool] -> [Int]
consec m = map (subtract m) . findIndices (>= m) . scanl (\ c x -> if x then c + 1 else 0) 0

ghci> consec 4 [False, True, True, True, True, True, False, True, True, True, True]
[1,2,7]

我真的很喜欢你的答案的可扩展性D即使我想要385个连续的假值,这也行@DanMaheshwari,我刚刚编辑了我的答案,以展示我的第二种方法如何扩展到其他数字,以及如何重构它。
(==True)
只是
id
,所以它是
findIndices id$map和$sliding 2b
。在每次递归调用时重新计算
length
是无效的,我要么定义
safeTake::Int->[a]>Maybe[a]
,要么搜索更好的方法
else滑动大小xs
else[]
。在这里使用
length
是一个非常糟糕的主意——它使算法成为O(n^2)。相反,为什么不
滑动n=dropLast n。地图(以n为例)。尾部
,以及带有常量xs(下降n xs)的水滴n xs=zip
?这是O(n),更好的是,正如你所希望的那样懒惰/高效。另外,
findIndices(\x->(x==True))。map(\x->all(=True)x)
的拼写可能更好
findIndices and
@DanielWagner@user3237465感谢您的评论,尤其是
id
。什么包是
dropWhile
in?