Reputation:
I'm new to Haskell.
For a Project Euler problem, I've generated a list of bools.
[True, False, False, True, False. . .True]
What I'd like is to write a function find the first four consecutive True's, then return their indices in the list.
Intuitively, I know I should be doing this with a fold and somehow pattern matching. Could you help me? Something like this with a fold? (I have no idea how to retrieve the index of an element.)
consecutiveFourTrues (w:x:y:z):xs = if (w && x && y && z) then w else consecutiveFourTrues (x:y:z):xs
Thank you for your help!
Upvotes: 2
Views: 1095
Reputation: 909
Maybe we should simply count the consecutive True
s rather than make sublists of them?
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]
Upvotes: 0
Reputation: 48591
The simplest way to do this is to zip and then use a specialty fold. First look at each set of four consecutive elements and see if they're all 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)
Now you just need
fstConsec4 :: [Bool] -> Maybe Int
fstConsec4 = findIndex id . consec4
Now this is not likely to be the very fastest you can do, and it doesn't generalize well to larger windows.
We can instead jump more directly into folds, taking a different approach. Note that this particular version is likely to behave better in GHC 7.10 or later than in earlier versions. I used bang patterns for clarity, but you could use seq
or $!
for portability if you like.
Let's start by defining a function conspicuously missing from 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)
Using this function, we can easily define a function that finds the index of the beginning of the first group of n
consecutive True
values.
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)
A quick look at the GHC core this produces suggests it will likely be quite efficient (this was produced using 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 }
In theory, it should be possible to do better, especially when looking in long lists. In particular, branching on the Bool
values may lead to many mis-predicted branches if True
and False
values are both frequent. In this case, it's probably better to shove the values into a small bit vector, then play some bitty tricks on it. Unfortunately, it can be tough to convince GHC to do this sort of thing, but I may try later.
Upvotes: 3
Reputation: 7138
Is using fold
a requirement?
Because otherwise it can be straightforwardly expressed with standard functions from Data.List
:
consec :: Int -> [Bool] -> [Int]
consec n = findIndices (isPrefixOf (replicate n True)) . tails
Just read it as English: findIndices
of all tails
of the list for which replicate
-ed n
times True
isPrefixOf
.
Upvotes: 2
Reputation: 12715
Here is another solution:
consec :: Int -> [Bool] -> [Int]
consec n = findIndices and . foldr (zipWith (:)) (repeat []) . take n . tails
It's based on ideas from other answers. take n . tails
returns a matrix, which has lists of consecutive elements of length <= n
as columns. foldr (zipWith (:)) (repeat [])
transposes the matrix and throws away lists of length < n
. findIndices
finds indices.
Upvotes: 1
Reputation: 20285
tldr
findIndices (\x -> (x == True)) $ map (\x -> all (== True) x) $ sliding 2 bs
Define a sliding
function similar to the one found in 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
Using sliding 2
with [True, False, False, True, True, False, True, False, True, True]
gives
[[True,False],[False,False],[False,True],[True,True],[True,False],[False,True],[True,False],[False,True],[True,True]]
Then we can map
to see if each sub list is contains all
True
values and get the indices of those that do with findIndices
.
findIndices (\x -> (x == True)) $ map (\x -> all (== True) x) $ sliding 2 bs
[3,8]
Upvotes: 4