I want to map a conditional function only on the first item that passes.
map (>5) [1,2,3,4,5,6,7,8,9]
would result in
[False,False,False,False,False,True,True,True,True]
I'm looking for something that would result in
[False,False,False,False,False,True,False,False,False]
So only the first occurrence of being greater than 5 results in True. I tried scanl, various folds and tried to roll my own mapUntil kind of thing. Seems like a simple problem but I'm drawing a blank.
break specifically separates the list in 2 parts where the first part is all False
, the opposite of span.
break (>5) [1,2,3,8,2,5,1,7,9]
>>> ([1,2,3],[8,2,5,1,7,9])
Then it's just what chi did:
oneTrue f lst = map (const False) a ++ rest b
where (a,b) = break f lst
rest [] = []
rest (x:xs) = True : map (const False) xs
A basic solution:
mapUntil p = onlyOne . map p
where
onlyOne [] = []
onlyOne (x:xs)
| x = True : map (const False) xs
| otherwise = False : onlyOne xs
With library helpers:
mapUntil p = snd . mapAccumL (\x y -> (x||y, not x && y)) False . map p
Above x
is a boolean standing for "have seen a true before?", as a kind-of state. y
is the list element. x||y
is the new state, while not x && y
is the new list element.
Alternatively (using Control.Arrow.second
):
mapUntil p = uncurry (++) . second go . break id . map p
where
go [] = []
go (x:xs) = x : map (const False) xs
I would use the mapAccumL
tool like;
λ> Data.List.mapAccumL (\b n -> if b then (b, (not b)) else (n > 5, n > 5)) False [1,2,3,4,5,6,7,8,9]
(True,[False,False,False,False,False,True,False,False,False])
Here we carry the b
as the state of our interim calculations and in every step decide according to it's previous state. Obviously you need the snd
part of the final result.
Edit : After reading the new comment of @Gord under his question I decided to extend my answer to cover his true problem.
Rephrasing the case event of
branch that starts with pointerPress (x,y)
into...
To start with, you never use x
or y
from the pattern match (x,y)
so lets call it c
. Then...
PointerPress c -> State circleCoords circleColors circleDraggeds c
where
bools = fmap checkMouseOverlaps $ (,) <$> circleCoords <*> [c]
circleDraggeds = snd $ mapAccumL (\a b -> if a then (a, not a)
else (b,b)) False bools
What's happening part;
(,) <$> circleCoords <*> [c]
circleCoords
is a list of coordinates like [c0,c1,c2]
and we fmap
(the infix version (<$>)
here) (,)
function to it and it becomes an applicative of coordinates like [(c0,),(c1,),(c2,)]
. Then we apply it to [c]
aka [(x,y)]
to turn it into [(c0,c),(c1,c),(c2,c)]
.
fmap checkMouseOverlaps $ toAbove
obviously yields to
[checkMouseOverlaps (c0,c), checkMouseOverlaps (c1,c), checkMouseOverlaps (c2,c)]
which is bools :: [Bool]
.
The the rest follows the logic explained at the top of my answer.
circleDraggeds = snd $ mapAccumL (\a b -> if a then (a, not a)
else (b,b)) False bools
This can be solve directly with recursion. Similar to chi's solution but without function composition
mapUntil :: (a -> Bool) -> [a] -> [Bool]
mapUntil _ [] = []
mapUntil f (x:xs) =
let b = f x -- calculate f x
in if b -- if true
then b : map (const False) xs -- prepend to the solution and map False to the rest of the list (b is True)
else b : mapUntil f xs -- keep applying mapUntil (b is False)
>>> mapUntil (>5) [1,2,3,4,5,6,7,8,9]
[False,False,False,False,False,True,False,False,False]
Map the condition over the list, then zip the result with the False
prefix of the result concatenated with a True
followed by an infinite list of False
s:
{-# LANGUAGE BlockArguments, ApplicativeDo, ViewPatterns #-}
import Control.Applicative (ZipList(..))
f :: (a -> Bool) -> [a] -> [Bool]
f cond (map cond -> bs) = getZipList do
r <- ZipList $ takeWhile not bs ++ [True] ++ repeat False
_ <- ZipList $ bs
pure r
or, equivalently:
f' :: (a -> Bool) -> [a] -> [Bool]
f' cond (map cond -> bs) = zipWith const (takeWhile not bs ++ [True] ++ repeat False) bs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With