· haskell

Haskell: A cleaner way of initialising a map

I recently wrote a blog post showing a way of initialising a Haskell map and towards the end of the post I realised how convoluted my approach was and wondered if there was an easier way and indeed there is!

To recap, this is the code I ended up with to populate a map with binary based values as the keys and node ids as the values:

import Data.Map 

toMap :: [Int] -> Map Int [Int]
toMap nodes = fromList $ map asMapEntry $ (groupIgnoringIndex . sortIgnoringIndex) nodesWithIndexes
              where nodesWithIndexes = (zip [0..] nodes)

groupIgnoringIndex = groupBy (\(_,x) (_,y) -> x == y) 
sortIgnoringIndex = sortBy (\(_,x) (_,y) -> x `compare` y)

asMapEntry :: [(Int, Int)] -> (Int, [Int]) 
asMapEntry nodesWithIndexes = 
   ((snd . head) nodesWithIndexes, Prelude.foldl (\acc (x,_) -> acc ++ [x]) [] nodesWithIndexes)

> assocs $ toMap [1,2,5,7,2,4]

To sum up what we're trying to do: when a key doesn't have an entry we want to create one with a list containing the appropriate value and if the key already has a value then we want to append that value to the existing list.

As it turns out the insertWith function does exactly what we want:

> let emptyMap = empty :: Map Int [Int]
> assocs $ foldl (\acc (id,val) -> insertWith (++) val [id] acc) emptyMap nodesWithIndexes

Based on this experience it would appear that the same type of thing applies when coding in Haskell as when coding in Clojure.

To paraphrase Jay Fields:

If you've written a fair amount of Clojure code [...] then chances are you've probably reinvented a few functions that are already available in the standard library.
  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket