Skip to content

Instantly share code, notes, and snippets.

@alexpw
Created November 21, 2014 21:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexpw/93ee2082f0cd2a54496d to your computer and use it in GitHub Desktop.
Save alexpw/93ee2082f0cd2a54496d to your computer and use it in GitHub Desktop.
haskell fold/scan reimplementation exercise
module FoldScan where
import Prelude hiding (foldl, foldl', foldl1,
foldr, foldr', foldr1,
scanl, scanl1, scanr, scanr1)
-- (map fn list) -> list
-- map is (1:1) with the original list and returns a copy of a list after applying a fn to each element
-- (filter fn list) -> list
-- filter is (n:0..n) with the original list and only returns those elements of a list which match a predicate fn
-- (reduce fn initial list) -> result
-- reduce is (n:0..n) with the original list transformed in anyway possible,
-- by a user fn that receives each element *and* the accumulated result up to that point.
-- Having control over the accumulated result is what gives reduce great power.
-- map, filter, and others can easily be expressed in terms of reduce
-- foldl is synonymous with reduce, but requires the initial starting value.
-- foldr is synonymous with reduce, but with the args for the user fn flipped.
-- foldl1/foldr1 variants do not require the starting value.
--foldl (+) 0 [1..5] -- => 15
--foldr (+) 0 [1..5] -- => 15
--foldl (-) 0 [1..5] -- => -15
--foldr (-) 0 [1..5] -- => 3
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl _ accumulator [] = accumulator
foldl fn accumulator (x:xs) = foldl fn (fn accumulator x) xs
-- std
foldr' :: (b -> a -> a) -> a -> [b] -> a
foldr' fn accumulator xs = foldl (flip fn) accumulator xs
-- tail-call
foldr :: (b -> a -> a) -> a -> [b] -> a
foldr fn accumulator = helper accumulator
where
helper accumulator [] = accumulator
helper accumulator (x:xs) = helper (fn x accumulator) xs
-- scanl (+) 0 [10,20,30] => [0,10,30,60]
-- scanr (+) 0 [10,20,30] => [60,50,30,0]
-- returns the intermediate values during a reduce operation
-- includes the starting value "0" in the result, from the example above.
-- the last element in the list returned by scanl is the expected result of reduce for the same arguments.
-- the first (head) element in the list returned by scanr is the "..."
-- scanl is equivalent to http://clojuredocs.org/clojure.core/reductions
-- (reductions fn initial coll)
-- std; copied from Prelude with clearer naming; tied for speed with scanl'
scanl :: (a -> b -> a) -> a -> [b] -> [a]
scanl fn accumulator xs = accumulator :
(case xs of
[] -> []
(x:xs) -> scanl fn (fn accumulator x) xs)
-- tail-call; using cons; tied with scanl for speed; less memory usage
scanl' :: (a -> b -> a) -> a -> [b] -> [a]
scanl' fn accumulator = reverse . helper [accumulator] accumulator
where
helper ys _ [] = ys
helper ys accumulator (x:xs) = helper (val : ys) val xs
where
val = fn accumulator x
-- tail-call; using concat; slowest and a memory hog
scanl'' :: (a -> b -> a) -> a -> [b] -> [a]
scanl'' fn accumulator = helper [accumulator] accumulator
where
helper ys _ [] = ys
helper ys accumulator (x:xs) = helper (ys ++ [val]) val xs
where
val = fn accumulator x
-- scanl (+) 0 [1..50000]
-- memory: 441.22 mb
-- time: 2.02 sec
-- scanl' (+) 0 [1..50000]
-- memory: 439.522 mb
-- time: 2.02 sec
-- scanl'' (+) 0 [1..50000]
-- memory: 111.126 gb
-- time: 370 sec
-- memory diff is stable: scanl' wins
-- time diff is tied between scanl and scanl' (at various input sizes)
-- winner overall: scanl'
-- note: used (:set +s) to measure time/memory;
-- note: granularity for time is too imprecise at small input sizes to comment
scanr :: (b -> a -> a) -> a -> [b] -> [a]
scanr fn initial xs = reverse $ scanl (flip fn) initial (reverse xs)
-- (hard) exercise for the reader: implement scanr more efficiently
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment