DEV Community

Cover image for F# For Dummys - Day 14 Collections Map
AllenZhu
AllenZhu

Posted on • Updated on

F# For Dummys - Day 14 Collections Map

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)]
Enter fullscreen mode Exit fullscreen mode
  • Map.ofList
let map = 
    Map.ofList [
        (3, "three")
        (1, "one")
        (2, "two")
    ]

printfn "map: %A" map // map [(1, one); (2, two); (3, three)]
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

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 []
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode
  • 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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)