Basics 15 - Collections (Basics)

Collections in F# are vastly different from collections available in System.Collections or System.Collections.Generic namespaces. While you are free to use System.Collections or System.Collections.Generic collections in F# (just like one does in C#), you will often find yourself moving against functional ideas.

There are five collection types in F#:

  1. array

  2. list

  3. seq

  4. map

  5. set

Sequences and Sequence Expressions

Sequences in F#, represented by seq is IEnumerable only. Essentially, anything which is enumerable. IEnumerable is often a source of confusion. IEnumerable is not a collection, it is an abstraction.

Iterating over an array, a list, a linked list, a dictionary, a hashtable or a binary tree can't be the same. IEnumerable provides an abstract way to iterate over various collection types. As a matter of fact, IEnumerable is not limited to collections.

Using Sequence Expressions to Create Sequences

Here are a few examples of how to use sequence expressions to create sequences.

// Sequence 1 to 3
seq { 1..3 } |> printfn "%A" // seq [1; 2; 3]

// Sequence 1 to 7, skip 2
seq { 1..2..7 } |> printfn "%A" // seq [1; 3; 5; 7]

// Loop 1 to 4, i * i for each value of i
seq { for i in 1..4 -> i * i } |> printfn "%A" // seq [1; 4; 9; 16]

// Loop 1 to 7, skip 2, i * i for each value of i
seq { for i in 1..2..7 -> i * i } |> printfn "%A" // seq [1; 9; 25; 49]

// Same as above, alternate mechanism
seq {
    for i in 1..4 do
        yield i * i
}
|> printfn "%A" // seq [1; 4; 9; 16]

// Same as above, alternate mechanism
seq {
    for i in 1..4 do
        i * i
}
|> printfn "%A" // seq [1; 4; 9; 16]

// Nested loop, creating tuple
seq {
    for x in 1..3 do
        for y in 1..3 -> (x, y)
}
|> printfn "%A" // seq [(1, 1); (1, 2); (1, 3); (2, 1); ...]

// Loop and condition
seq {
    for n in 1..10 do
        if n % 2 = 0 then
            n
}
|> printfn "%A" // seq [2; 4; 6; 8; ...]

Iterating over Sequences

Here are a few examples of how to iterate over sequences.

// Sequence of int
let s =   seq { 1..3 }

// Iterate over int values
for i in s do
    printfn "%d" i

// Sequence of tuple
let sTup =
    seq {
        for x in 1..3 do
            for y in 1..3 -> (x, y)
    }

// Patern matching, deconstruction and iteration
for (x, y) in sTup do
    printfn "x: %d, y: %d" x y

Using Sequences as the Abstract Data Type

Sequences are best used as abstract data types.

Here's an example:

type Student =
    { Name: string
      Age: int
      Marks: int array }

let s =
    { Name = "Whatever"
      Age = 10
      Marks = [| 1; 2; 3 |] }

The Student record type has Marks of the type int array. This means an instance of Student can only be created with an int array. What if you have an int list?

Now consider this:

type Student =
    { Name: string
      Age: int
      Marks: int seq }

// using int array
let s1 =
    { Name = "Whatever"
      Age = 10
      Marks = [| 1; 2; 3 |] }

// using int list
let s2 =
    { Name = "Whatever"
      Age = 10
      Marks = [ 1; 2; 3 ] }

// using int seq
let s1 =
    { Name = "Whatever"
      Age = 10
      Marks = seq { 1..3 } }

With the updated definition of Student, int array, int list and int seq, all are allowed.

Similarly, when creating functions, make them generalized. For instance:

let findMax (numbers: int list) = ... vs.

let findMax (numbers: int seq) = ...

Arrays

Arrays are fixed-size collections. Indexing is zero-based and arrays allow mutations.

Here are various ways in which arrays can be created:

// Fixed values
let arr1 = [| 1; 2; 3 |]

// expression
let arr2 = [| 1..3 |]

// expression
let arr3 = [| for i in 1..3 -> i |]

// Creates an array with 3 elements, all set to 0
let arr4 = Array.create 3 0

// Value mutation
arr4[0] <- 1
arr4[1] <- 2
arr4[2] <- 3

// Creates an array with 3 elements, all set to 0
let arr5 = Array.zeroCreate 3

// Value mutation
arr5[0] <- 1
arr5[1] <- 2
arr5[2] <- 3

printfn "%A" arr1 // [|1; 2; 3|]
printfn "%A" arr2 // [|1; 2; 3|]
printfn "%A" arr3 // [|1; 2; 3|]
printfn "%A" arr4 // [|1; 2; 3|]
printfn "%A" arr5 // [|1; 2; 3|]

How to access array items and iterate over an array:

let arr = [| 1; 2; 3 |]

// access array items
let a = arr[0]
let b = arr[1]
let c = arr[2]

// access array items
let a1 = arr.GetValue 0
let b1 = arr.GetValue 1
let c1 = arr.GetValue 2

// set values
arr[0] <- 5
arr[1] <- 6
arr[2] <- 7

// set values
arr.SetValue(5, 0)
arr.SetValue(6, 1)
arr.SetValue(7, 2)

// prints all array items
for i in arr do
    printfn "%d" i

// iter function invoke function for each item
Array.iter (fun x -> printfn "%d" x) arr // prints all array items

Lists

Lists in F# are immutable collections. Indexing is zero-based.

Here are various ways in which lists can be created:

// Empty list
let list0 = []

// Fixed values
let list1 = [ 1; 2; 3 ]

// expression
let list2 = [ 1..3 ]

// expression
let list3 = [ for i in 1..3 -> i ]

// Add an item in the beginning
// :: aka cons operators
let list4 = 1 :: [ 2; 3 ]

// Contact 2 lists
let list5 = [ 1 ] @ [ 2; 3 ]

// Contact or add at the end
let list6 = [ 1; 2 ] @ [ 3 ]

printfn "%A" list0 // [0]
printfn "%A" list1 // [1; 2; 3]
printfn "%A" list2 // [1; 2; 3]
printfn "%A" list3 // [1; 2; 3]
printfn "%A" list4 // [1; 2; 3]
printfn "%A" list5 // [1; 2; 3]
printfn "%A" list6 // [1; 2; 3]

How to access list items and iterate over a list:

let list1 = [ 1; 2; 3 ]

// access list items
let a = list1[0]
let b = list1[1]
let c = list1[2]

// access list items
let a1 = list1.Item 0
let b1 = list1.Item 1
let c1 = list1.Item 2

// prints all list items
for i in list1 do
    printfn "%d" i

// iter function invoke function for each item
List.iter (fun x -> printfn "%d" x) list1 // prints all list items

Pattern Matching, Head and Tail

Head refers to the first element of the list, while Tail refers to the list after the first element. A list can be pattern matched using :: (cons operator) into head/tail.

Here is an example:

let l = [ 1; 2; 3 ]

let head = l.Head
let tail = l.Tail

printfn "%A" head // 1
printfn "%A" tail // [2; 3]

// recursive function for finding sum
// may cause stack overflow for a large list
let rec sum list =
    match list with
    | head :: tail -> head + sum tail
    | [] -> 0

// recursive function for finding sum
// no stack overflow, tail recursion
let sumFixed list =
    let rec loop list acc =
        match list with
        | head :: tail -> loop tail (acc + head)
        | [] -> acc
    loop list 0

// recursive function for finding min
// no stack overflow, tail recursion
let min list =
    if List.length list = 0 then
        raise (System.ArgumentException("Empt list provided!"))
    else
        let rec loop list acc =
            match list with
            | head :: tail -> loop tail (if head < acc then head else acc)
            | [] -> acc
        loop list Int32.MaxValue

// recursive function for finding max
// no stack overflow, tail recursion
let max list =
    if List.length list = 0 then
        raise (System.ArgumentException("Empt list provided!"))
    else
        let rec loop list acc =
            match list with
            | head :: tail -> loop tail (if head > acc then head else acc)
            | [] -> acc
        loop list Int32.MinValue

sum l |> printfn "%A" // 6
min l |> printfn "%A" // 1
max l |> printfn "%A" // 3

Sets

Sets in F# are immutable collections. Sets are ordered which are based on binary trees.

Here are various ways in which sets can be created:

// An empty set
let s = Set.empty

printfn "%A" s // set []

// Add 1 to the empty set
let s = s.Add 1

printfn "%A" s // set [1]

// Can't add duplicate value
// Adding 1 again will have no effect
let s = s.Add 1

printfn "%A" s // set [1]

// Adding 2 and 3
let s = s.Add(2).Add(3)

printfn "%A" s // set [1; 2; 3]

// Array to Set
let s = Set.ofArray [| 1; 2; 3 |]

printfn "%A" s // set [1; 2; 3]

// Can't add duplicate value
// Array to Set
let s = Set.ofArray [| 1; 1; 2; 3 |]

printfn "%A" s // set [1; 2; 3]

// List to Set
let s = Set.ofList [ 1; 2; 3 ]

printfn "%A" s // set [1; 2; 3]

// Sequence to Set
let s = Set.ofSeq { 1..3 }

printfn "%A" s // set [1; 2; 3]

How to iterate over a set:

let s = Set.ofArray [| 1; 1; 2; 3 |]

// prints all set items
for i in s do
    printfn "%d" i

// iter function invoke function for each item
Set.iter (fun x -> printfn "%d" x) s // prints all set items

Maps

Maps in F# are immutable collections. Maps represent key/value pairs aka dictionaries.

Here are various ways in which maps can be created:

// An empty map
let m = Map.empty

printfn "%A" m // map []

// Add an item (Key, Value)
let m = m.Add(1, "One")

printfn "%A" m // map [(1, "One")]

// Adding with the same key results in value update
let m = m.Add(1, "OneOne")

printfn "%A" m // map [(1, "OneOne")]

// Adding many values
let m = m.Add(1, "One")
let m = m.Add(2, "Two")
let m = m.Add(3, "Three")

printfn "%A" m // map [(1, "One"); (2, "Two"); (3, "Three")]

// Array to Map
let m = Map.ofArray [| (1, "One"); (2, "Two"); (3, "Three") |]

printfn "%A" m // map [(1, "One"); (2, "Two"); (3, "Three")]

// List to Map
let m = Map.ofList [ (1, "One"); (2, "Two"); (3, "Three") ]

// Sequence to Map
printfn "%A" m // map [(1, "One"); (2, "Two"); (3, "Three")]

let m =
    Map.ofSeq (
        seq {
            yield (1, "One")
            yield (2, "Two")
            yield (3, "Three")
        }
    )

printfn "%A" m // map [(1, "One"); (2, "Two"); (3, "Three")]

How to access map items and iterate over a map:

let m = Map.ofList [ (1, "One"); (2, "Two"); (3, "Three") ]

let x1 = m[1] // x1 is "One"
let y1 = m[100] // exception... key not found

let x2 = m.TryFind(1) // x2 is Some("One")
let y2 = m.TryFind(100) // y2 is None

let x3 = m.TryGetValue 1 // x3 is (true, "One")
let xy = m.TryGetValue 100 // y3 is (false, )

// prints all map items
// i = KeyValuePair<TypeKey, TypeValue?
for kv in m do
    printfn "Key: %A, Value: %A" kv.Key kv.Value

Map.iter (fun k v -> printfn "Key: %A, Value: %A" k v) m

If you have reached so far, congratulations.

Keep reading!