How to break out from a fold function in haskell when the accumulator met a certain condition?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



How to break out from a fold function in haskell when the accumulator met a certain condition?



I'm calculating the sum of a list after applying someFunction to every element of it like so:


someFunction


sum (map someFunction myList)



someFunction is very resource heavy so to optimise it I want to stop calculating the sum if it goes above a certain threshold.


someFunction



It seems like I need to use fold but I don't know how to break out if it if the accumulator reaches the threshold. My guess is to somehow compose fold and takeWhile but I'm not exactly sure how.


fold


takeWhile





Almost, but use scanl instead of fold.
– n.m.
Aug 7 at 13:07





If the sum goes above a threshold or if the outcome of your resource intensive function goes above a threshold?
– Elmex80s
Aug 8 at 7:59





7 Answers
7



One of the options would be using scanl function, which returns a list of intermediate calculations of foldl.


foldl



Thus, scanl1 (+) (map someFunction myList) will return the intermediate sums of your calculations. And since Haskell is a lazy language it won't calculate all the values of myList until you need it. For example:


scanl1 (+) (map someFunction myList)


Haskell


myList


take 5 $ scanl1 (+) (map someFunction myList)



will calculate someFunction 5 times and return the list of these 5 results.


someFunction



After that you can use either takeWhile or dropWhile and stop the calculation, when a certain condition is True. For example:


True


head $ dropWhile (< 1000) $ scanl1 (+) [1..1000000000]



will stop the calculation, when sum of the numbers reaches 1000 and returns 1035.


1035





Perhaps a case to take into consideration is that it is possible that the threshold is never reached, in which case head will error.
– Willem Van Onsem
Aug 7 at 14:05


head





@WillemVanOnsem yea, thanks for mentioning it here
– Igor Drozdov
Aug 7 at 14:25



Another technique is to use a foldM with Either to capture the early termination effect. Left signals early termination.


foldM


Either


Left


import Control.Monad(foldM)

sumSome :: (Num n,Ord n) => n -> [n] -> Either n n
sumSome thresh = foldM f 0
where
f a n
| a >= thresh = Left a
| otherwise = Right (a+n)



Use a bounded addition operator instead of (+) with foldl.


(+)


foldl


foldl (b a -> b + if b > someThreshold then 0 else a) 0 (map someFunction myList)



Because Haskell is non-strict, only calls to someFunction that are necessary to evaluate the if-then-else are themselves evaluated. fold still traverses the entire list.


someFunction


if-then-else


fold


> foldl (b a -> b + if b > 10 then 0 else a) 0 (map (trace "foo") [1..20])
foo
foo
foo
foo
foo
15



sum [1..5] > 10, and you can see that trace "foo" only executes 5 times, not 20.


sum [1..5] > 10


trace "foo"



Instead of foldl, though, you should use the strict version foldl' from Data.Foldable.


foldl


foldl'


Data.Foldable.





This is cool, and seems like witchcraft, but applied to an infinite list it does hang. I think the trace shows that a just isn't forced, although the whole list is traversed.
– trevor cook
Aug 7 at 16:15


a





Ah, right. I couldn't quite put my finger on why this seemed to work anyway.
– chepner
Aug 7 at 17:34





I have been thinking that this lazyness trick is a canonical way to "break out" from folds.
– arrowd
Aug 8 at 9:22



You could try making your own sum function, maybe call it boundedSum that takes


sum


boundedSum



an Integer upper bound


Integer



an [Integer] to sum over


[Integer]



a "sum up until this point" value to be compared with the upper bound



and returns the sum of the list.


boundedSum :: Integer -> [Integer] -> Integer -> Integer
boundedSum upperBound (x : xs) prevSum =
let currentSum = prevSum + x
in
if currentSum > upperBound
then upperBound
else boundedSum upperBound xs currentSum
boundedSum upperBound prevSum =
prevSum



I think this way you won't "eat up" more of the list if the sum up until the current element exceeds upperBound.



EDIT: The answers to this question suggest better techniques than mine and the question itself looks rather similar to yours.



This will do what you ask about without building the intermediate list as scanl' would (and scanl would even cause a thunks build-up on top of that):


scanl'


scanl


foldl'Breaking break reduced reducer acc list =
foldr cons (acc -> acc) list acc
where
cons x r acc | break acc x = reduced acc x
| otherwise = r $! reducer acc x



cf. related wiki page.



This is a possible solution:


last . takeWhile (<=100) . scanl (+) 0 . map (^2) $ [1..]



Dissected:


[1..]


(^2)


scanl (+) 0


(<=100)



If performance matters, also try scanl', which might improve it.


scanl'



Something like this using until :: (a -> Bool) -> (a -> a) -> a -> a from the Prelude


until :: (a -> Bool) -> (a -> a) -> a -> a


sumUntil :: Real a => a -> [a] -> a
sumUntil threshold u = result

where

(_, result) = until stopCondition next (u, 0)

next :: Real a => ([a], a) -> ([a], a)
next ((x:xs), y) = (xs, x + y)

stopCondition :: Real a => ([a], a) -> Bool
stopCondition (ls, x) = null ls || x > threshold



Then apply


sumUntil 10 (map someFunction myList)






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

make 2 or more post in bootsrap

Store custom data using WC_Cart add_to_cart() method in Woocommerce 3

Firebase Auth - with Email and Password - Check user already registered