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

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

Billboard image

Imagine monitoring that's actually built for developers

Join Vercel, CrowdStrike, and thousands of other teams that trust Checkly to streamline monitor creation and configuration with Monitoring as Code.

Start Monitoring

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay