26/05/2009
Just a quick implementation of a RLE algorithm for lists in haskell. We compress a list by converting “runs” of consecutive elements into a tuple of the form: (run_length, element).
import Data.List (group)
import Control.Arrow
runLengthEnc :: Eq a => [a] -> [(Int,a)]
runLengthEnc = map (length &&& head) . group
decode :: [(Int, a)] -> [a]
decode = concatMap (uncurry replicate)
If the &&& combinator looks foreign to you, check out David R. Maciver’s very enlightening blog about Arrow functions.
I’m always curious to see how naive-looking functions like the above compare in performance to a from-scratch implementation with explicit recursion, so I came up with the following:
runLengthEnc' :: Eq a => [a] -> [(Int,a)]
runLengthEnc' [] = []
runLengthEnc' (a:as) = run 1 a as
where run n x [] = [(n,x)]
run n x (x2:xss) | x == x2 = run (n+1) x xss
| otherwise = (n,x) : run 1 x2 xss
I tested both functions on a list of 100,000 random 1s and 0s and found the explicit version to be only marginally better performing, completing the list in 49 ticks & 130Mb, compared to 54 ticks & 139 Mb for the one-liner!
17/05/2009
I thought this was a good, simple example of how currying and partial application can be used in a very expressive way. We create a one-liner to play the game mastermind.
In the type signature we include unnecessary parentheses, to be clear that we are thinking of the function not as “a function taking a secret code and a guess and returning a score for the guess” but as “a function taking a secret code and returning a scoring function for that code”.
module MasterMind
where
score :: Eq a => [a] -> ([a] -> (Int,Int))
score c = flip (,) (length c) . length . filter id . zipWith (==) c
game = score [4,3,5,0]
11/05/2009
In my boredom, I implemented the toy sorting algorithm called Gnome Sort. It accomplishes backtracking by using a zipper-like system of two lists treated as stacks, and is about 100X slower than GHC’s merge sort on the test I used. It’s also not especially small or simple-looking. It’s basically terrible:
gnomeSort :: (Ord a)=> [a] -> [a]
gnomeSort = sort []
where sort rs [] = rs
sort [] (x:xs) = sort [x] xs
sort (r:rs) (x:xs)
| x > r = sort rs (x:r:xs)
| otherwise = sort (x:r:rs) xs
The above doesn’t keep track of where it left off before it began to backtrack, an obvious optimization which turned the algorithm into an insertion sort:
import Data.List (insert)
insertSort :: (Ord a)=> [a] -> [a]
insertSort = sort []
where sort rs [] = rs
sort rs (x:xs) = sort (insert x rs) xs
about 60X slower than Data.List.sort
.