While examining ways to refactor the PFP library, it struck me that its strategy of storing repeated values and dealing with displaying them as an aggregate later was somewhat generalizable. This is a first stab at a counter datatype with constant-time update and (probably) O(m) lookup — where m is the sum of counts. The alternate strategy, using a Map, has O(log(n)) lookup and update time — where n is the number of counters.

What do we want to do here?



module Counter (count, inc, dec, empty, fromCounter, toCounter) where



We want to store a list of values together with counts, e.g.



*Counter> example

"Rock" 18

"Jazz" 27

"Classical" 13

" Etc." 35



We should be able to retrieve any count



*Counter> count "Rock" example

18



We want cheap inc



*Counter> inc "Rock" example

"Rock" 19

"Jazz" 27

"Classical" 13

" Etc." 35



inc should handle gracefully (and still cheaply) new values:



*Counter> inc "Traditional" example

"Traditional" 1

"Rock" 18

"Jazz" 27

"Classical" 13

" Etc." 35



There should be a functioning dec that also handles invalid cases gracefully:



*Counter> dec "Classical" example

"Rock" 18

"Jazz" 27

"Classical" 12

" Etc." 35

*Counter> dec "Mbembe" example

"Rock" 18

"Jazz" 27

"Classical" 13

" Etc." 35



There should be a simply empty counter so new counters can be constructed via inc chains:



*Counter> inc False $ inc True $ inc False $ inc True empty

False 2

True 2



Finally, there should be convenience functions to construct a counter from a list of (key, count) pairs



*Counter> toCounter [("Bjork",15),("Gentle Giant",12)]

"Bjork" 15

"Gentle Giant" 12



and to destruct a counter into such a list



*Counter> fromCounter example

[("Rock",18),("Jazz",27),("Classical",13),(" Etc.",35)]



Here’s how we implement it.

A counter is merely a list of values.



data Counter a = C {unC :: [a] }



unC is not a field name, it’s an automatically generated destructor. Maybe I’m catching a bad habit from Erwig.

We increment a key’s counter by consing it to the beginning of that list:



inc, dec :: (Eq a)=> a -> Counter a -> Counter a

inc x = C . (x:) . unC



We decrement a counter by removing the first appearance of the key in the list



dec x (C y) = case (findIndex (==x) y) of

Just a -> C (take a y ++ drop (a+1) y)

Nothing -> C y



This is the empty counter:



empty :: Counter a

empty = C []



We retrieve a counter by, oh me, counting:



count ::(Eq a) => a -> Counter a -> Int

count x = length . filter (==x) . unC



The counter is trivially destructed into a list of (key,value) pairs from this function:



fromCounter :: (Eq a) => Counter a -> [(a, Int)]

fromCounter (C x ) = map (\y->(y,count y (C x))) (nub x)



To construct a counter, we employ an auxiliary function for n-increasing a count; we don’t export it elsewhere.



toCounter :: (Eq a) => [(a, Int)] -> Counter a

toCounter [] = empty

toCounter ((a,b):xs) = ninc b a (toCounter xs) where

ninc :: (Eq a) => Int -> a -> Counter a -> Counter a

ninc 0 x y = y

ninc n x y = inc x (ninc (n-1) x y)



Finally, our Show method. Beware, it’s kind of ugly. First we display one key and its count



countShow :: (Eq a, Show a) => a -> Counter a -> String

countShow y x = (show y) ++ "\t" ++ (show (count y x))++"

"



Then we define this ugly auxiliary function to keep track both of the whole counter and of the part that remains to count.



show_ :: (Eq a, Show a) => [a] -> [a]-> String

show_ _ [] = ""

show_ everything remaining = countShow (head remaining) (C everything) ++ show_ everything (tail remaining)



From here show is trivial.



instance (Eq a, Show a) => Show (Counter a) where show (C x) = show_ x (nub x)



I hope I’m not reinventing the wheel. Or if I am, I hope it’s a smart wheel I’m reinventing!