DEV Community

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

Posted on • Edited 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
  • Change

Map.change key func table

Returns a new map with the value stored under key changed according to func.

let input = Map [ (1, "a"); (2, "b") ]

let changed_map = input |> Map.change 1 (fun x ->
    match x with
    | Some s -> Some (s + "z")
    | None -> None
)
printfn "changed map: %A" changed_map // evaluates to map [(1, "az"); (2, "b")]
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

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay