Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell map until first condition met

Tags:

haskell

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.

like image 930
Gord Avatar asked Apr 22 '20 07:04

Gord


5 Answers

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
like image 185
FrownyFrog Avatar answered Oct 09 '22 08:10

FrownyFrog


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
like image 38
chi Avatar answered Oct 09 '22 07:10

chi


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
like image 24
Redu Avatar answered Oct 09 '22 09:10

Redu


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]
like image 1
lsmor Avatar answered Oct 09 '22 09:10

lsmor


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 Falses:

{-# 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
like image 1
danidiaz Avatar answered Oct 09 '22 07:10

danidiaz