F# For Dummys – Day 14 Collections Map

RMAG news

Today we learn Map, an immutable collection that stores key-value pairs

Map is immutable, it cannot be changed after created. Adding or removing elements return a new Map instead of modifying the existing one

Create Map

Explicitly specifying elements

let notEmptyMap = Map [ (1, “a”); (2, “b”) ]

printfn “%A” notEmptyMap // map [(1, a); (2, b)]

Map.ofList

let map =
Map.ofList [
(3, “three”)
(1, “one”)
(2, “two”)
]

printfn “map: %A” map // map [(1, one); (2, two); (3, three)]

we can see the key is sorted from 3,1,2 to 1,2,3
what if the key is dulpicate

let map =
Map.ofList [
(3, “three”)
(1, “one”)
(2, “two”) // first key 2
(2, “overwrite”) // second key 2
]

printfn “%A” map // map [(1, one); (2, overwrite); (3, three)]

the value of the same key 2 is the last one, it overwrite the first one

Map.empty
create an empty map with int keys and string values

let emptyMap = Map.empty<int, string>
printfn “%A” emptyMap // map []

Access element

ContainsKey: Tests if an element is in the domain of the map

let sample = Map [ (1, “a”); (2, “b”) ]

printfn “sample contain key 1: %b” (sample.ContainsKey 1) // sample contain key 1: true
printfn “sample contain key 3: %b” (sample.ContainsKey 3) // sample contain key 3: false

map.[key]:
access by key in the domain will return the value, raise Exception if key not exists

let sample = Map [ (1, “a”); (2, “b”) ]

printfn “access by key 1: %s” sample.[1] // access by key 1: a
printfn “access by key 3: %s” sample.[3] // Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary

if you use browser environment, you may not see this Exception

TryFind: Lookup an element in the map, returning a Some value if the element is in the domain of the map and None if not

let sample = Map [ (1, “a”); (2, “b”) ]

printfn “TryFind key 1: %A” (sample.TryFind 1) // evaluates to Some “a”
printfn “TryFind key 3: %A” (sample.TryFind 3) // evaluates to None

TryGetValue: Lookup an element in the map, assigning to value if the element is in the domain of the map and returning false if not

let sample = Map [ (1, “a”); (2, “b”) ]

printfn “access by key 1: %A” (sample.TryGetValue 1) // evaluates to (true, “a”)
printfn “access by key 3: %A” (sample.TryGetValue 3) // evaluates to (false, null)

TryGetValue is added in F# 6.0
Conclusion: TryFind TryGetValueThe provide a safer way to access elements, and option type returned by TryFind TryGetValueThe fits naturally with F#’s pattern matching

Pattern Matching with Map

TryFind match

let mp = Map.ofList [(“doot”, 1); (“beef”, 2); (“hoopty”, 3)]

match Map.tryFind “doot” mp with
| Some value -> printfn “Value: %A” value
| None -> printfn “No value found!”

match Map.tryFind “goot” mp with
| Some value -> printfn “Value: %A” value
| None -> printfn “No value found!”

TryGetValue match

let mp = Map.ofList [(“doot”, 1); (“beef”, 2); (“hoopty”, 3)]

match mp.TryGetValue “doot” with
| (true, value) -> printfn “Value: %A” value
| (false, _) -> printfn “No value found!” // Value: 1

match mp.TryGetValue “goot” with
| (true, value) -> printfn “Value: %A” value
| (false, _) -> printfn “No value found!” // No value found!

Loop Map

for KeyValue(key, value) in

let map = Map.ofList [(“doot”, 1); (“beef”, 2); (“hoopty”, 3)]

for KeyValue(key, value) in map do
printfn “Key: %s, Value: %d” key value

Map.iter

let map = Map.ofList [(“doot”, 1); (“beef”, 2); (“hoopty”, 3)]
map |> Map.iter (fun key value -> printfn “Key: %s, Value: %d” key value)

Modify element of Map

Add & Remove

let map = Map.ofList [(“doot”, 1); (“beef”, 2); (“hoopty”, 3)]

let mapWithAddedElement = map.Add(“pork”, 4)
printfn “Map after adding (4, “four”): %A” mapWithAddedElement

let mapWithRemovedElement = mapWithAddedElement.Remove(“beef”)
printfn “Map after removing key 2: %A” mapWithRemovedElement

Practice

Counting Words in text: “hello world hello map in F#”

let text = “hello world hello map in F#”
let words = text.Split(‘ ‘) // [“hello”, “world”, “hello”, “map”, “in”, “F#”]

let wordCountMap =
words
|> Array.fold (fun acc word ->
if Map.containsKey word acc then // Static Member Function
let count = acc.[word]
acc.Add(word, count + 1)
else
acc.Add(word, 1)
) Map.empty

printfn “Word count map: %A” wordCountMap // Word count map: map [(F#, 1); (hello, 2); (in, 1); (map, 1); (world, 1)]

the solution use Map.containsKey which is Static Member Function, we can use acc Instance Member Function like this:

let text = “hello world hello map in F#”
let words = text.Split(‘ ‘)
printfn “words: %A” words

let wordCountMap =
words
|> Array.fold (fun (acc: Map<string, int>) word ->
if acc.ContainsKey word then // Instance Member Function
let count = acc.[word]
acc.Add(word, count + 1)
else
acc.Add(word, 1)
) Map.empty

printfn “Word count map: %A” wordCountMap

Static Member Function and Instance Member Function will be introduced in OOP