我是Haskell的新手。
对于Project Euler问题,我已经生成了一个bool列表。
[True, False, False, True, False. . .True]
我想要的是写一个函数找到前四个连续的True,然后在列表中返回它们的索引。
直观地说,我知道我应该通过折叠和某种模式匹配来做到这一点。你可以帮帮我吗?折叠的东西是这样的吗? (我不知道如何检索元素的索引。)
consecutiveFourTrues (w:x:y:z):xs = if (w && x && y && z) then w else consecutiveFourTrues (x:y:z):xs
感谢您的帮助!
答案 0 :(得分:4)
<强> tldr 强>
findIndices (\x -> (x == True)) $ map (\x -> all (== True) x) $ sliding 2 bs
定义一个类似于Scala中的sliding
函数。
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
将sliding 2
与[True, False, False, True, True, False, True, False, True, True]
一起使用
[[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]
答案 1 :(得分:3)
最简单的方法是拉链然后使用专业折叠。首先看看每组四个连续元素,看看它们是否都是真的:
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或更高版本中的行为可能比早期版本更好。为了清晰起见,我使用了爆炸模式,但如果您愿意,可以使用seq
或$!
来实现可移植性。
让我们从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)
使用此函数,我们可以轻松定义一个函数,该函数查找第一组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-type-signatures
生成的):
$wconsec :: Int# -> [Bool] -> Maybe Int
$wconsec =
\ (ww_s1BQ :: Int#) (w_s1BN :: [Bool]) ->
letrec {
$wgo_s1BL :: [Bool] -> Int# -> Int# -> Maybe Int
$wgo_s1BL =
\ (w1_s1BA :: [Bool]) (ww1_s1BF :: Int#) (ww2_s1BJ :: Int#) ->
case w1_s1BA of _ {
[] -> Nothing;
: y_a1y5 ys_a1y6 ->
case y_a1y5 of _ {
False -> $wgo_s1BL ys_a1y6 (+# ww1_s1BF 1) ww_s1BQ;
True ->
case ww2_s1BJ of ds_X16V {
__DEFAULT -> $wgo_s1BL ys_a1y6 (+# ww1_s1BF 1) (-# ds_X16V 1);
1 -> Just (I# (+# (-# ww1_s1BF ww_s1BQ) 1))
}
}
}; } in
$wgo_s1BL w_s1BN 0 ww_s1BQ
consec :: Int -> [Bool] -> Maybe Int
consec =
\ (w_s1BM :: Int) (w1_s1BN :: [Bool]) ->
case w_s1BM of _ { I# ww1_s1BQ -> $wconsec ww1_s1BQ w1_s1BN }
理论上,应该可以做得更好,特别是在查看长名单时。特别是,如果Bool
和True
值都很常见,则False
值上的分支可能会导致许多错误预测的分支。在这种情况下,将值推入一个小位向量可能更好,然后在它上面做一些小技巧。不幸的是,说服GHC做这类事情可能很难,但我可能会稍后再试。
答案 2 :(得分:2)
是否要求使用fold
?
因为它可以使用Data.List
:
consec :: Int -> [Bool] -> [Int]
consec n = findIndices (isPrefixOf (replicate n True)) . tails
只需将其读作英语:findIndices
列表的所有tails
replicate
- n
次True
isPrefixOf
。
答案 3 :(得分:1)
这是另一种解决方案:
consec :: Int -> [Bool] -> [Int]
consec n = findIndices and . foldr (zipWith (:)) (repeat []) . take n . tails
它基于其他答案的想法。 take n . tails
返回一个矩阵,其中包含长度为<= n
的连续元素列表。 foldr (zipWith (:)) (repeat [])
转置矩阵并丢弃长度为< n
的列表。 findIndices
找到索引。
答案 4 :(得分:0)
也许我们应该只计算连续的True
而不是制作它们的子列表?
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]