F# Training
F# Training
F# Training
  • Presentation
  • Fundamentals
    • Introduction
    • Syntax
      • Bases
      • Functions
      • Rules
      • Exceptions
    • First concepts
    • 🍔Quiz
  • Functions
    • Signature
    • Concepts
    • Syntax
    • Standard functions
    • Operators
    • Addendum
    • 🍔Quiz
    • 📜Summary
  • Types
    • Overview
    • Tuples
    • Records
    • Unions
    • Enums
    • Anonymous records
    • Value types
    • 📜Recap
    • Addendum
  • Monadic types
    • Intro
    • Option type
    • Result type
    • Smart constructor
    • 🚀Computation expression (CE)
    • 🚀CE theoretical basements
    • 📜Recap
  • Pattern matching
    • Patterns
    • Match expression
    • Active patterns
    • 🚀Fold function
    • 📜Recap
    • 🕹️Exercises
  • Collections
    • Overview
    • Types
    • Common functions
    • Dedicated functions
    • 🍔Quiz
    • 📜Recap
  • Asynchronous programming
    • Asynchronous workflow
    • Interop with .NET TPL
    • 📜Recap
  • Module and Namespace
    • Overview
    • Namespace
    • Module
    • 🍔Quiz
    • 📜Recap
  • Object-oriented
    • Introduction
    • Members
    • Type extensions
    • Class, Struct
    • Interface
    • Object expression
    • Recommendations
Powered by GitBook
On this page
  • Access to an element
  • Cost ⚠️
  • Combine collections
  • Find an element
  • Search elements
  • Select elements
  • Map elements
  • map vs mapi
  • Alternative to mapi
  • map vs iter
  • choose, pick, tryPick
  • Aggregate
  • Specialized aggregate functions
  • CountBy
  • Max(By), Min(By)
  • Sum(By), Average(By)
  • Monoid constraint
  • Versatile aggregate functions
  • Fold(Back) versatility
  • Change the order of elements
  • Separate
  • Group items
  • By size
  • By criteria
  • Change collection type
  • Functions vs comprehension
  • Additional resources

Was this helpful?

Edit on GitHub
  1. Collections

Common functions

Functions available in multiple modules, each version customized for its target type

Operations: access, construct, find, select, aggregate...

☝️ Convention used here:

  1. Functions are given by their name

    • To use them, we need to qualify them by the module.

  2. The last parameter is omitted for brevity

    • It's always the collection.

Example: head vs List.head x

Access to an element

↓ Access \ Return element →
Directly or 💣
Optionally

By index

x[index]

By index

item index

tryItem index

First element

head

tryHead

Last element

last

tryLast

💣 ArgumentException or IndexOutOfRangeException

[1; 2] |> List.tryHead    // Some 1
[1; 2] |> List.tryItem 2  // None

Cost ⚠️

↓ Function \ Module →
Array
List
Seq

head

O(1)

O(1)

O(1)

item

O(1)

O(n) ❗

O(n) ❗

last

O(1)

O(n) ❗

O(n) ❗

length

O(1)

O(n) ❗

O(n) ❗

Combine collections

Function
Parameters
Final size

append / @

2 collections of sizes N1 et N2

N1 + N2

concat

K collections of sizes N1..Nk

N1 + N2 + ... + Nk

zip

2 collections of same size N ❗

N pairs

allPairs

2 collections of sizes N1 et N2

N1 * N2 pairs

List.concat [ [1]; [2; 3] ];;  // [1; 2; 3]
List.append [1;2;3] [4;5;6];;  // [1; 2; 3; 4; 5; 6]

// @ operator: alias of `List.append` only • not working with Array, Seq
[1;2;3] @ [4;5;6];;            // [1; 2; 3; 4; 5; 6]

List.zip      [1; 2] ['a'; 'b'];;  // [(1, 'a'); (2, 'b')]
List.allPairs [1; 2] ['a'; 'b'];;  // [(1, 'a'); (1, 'b'); (2, 'a'); (2, 'b')]

Find an element

Using a predicate f : 'T -> bool:

↓ Which element \ Returns →
Direct or 💣
Optional

First found

find

tryFind

Last found

findBack

tryFindBack

Index of first found

findIndex

tryFindIndex

Index of last found

findIndexBack

tryFindIndexBack

[1; 2] |> List.find (fun x -> x < 2)      // 1
[1; 2] |> List.tryFind (fun x -> x >= 2)  // Some 2
[1; 2] |> List.tryFind (fun x -> x > 2)   // None

Search elements

Search
How many items
Function

By value

At least 1

contains value

By predicate f

At least 1

exists f

"

All

forall f

[1; 2] |> List.contains 0      // false
[1; 2] |> List.contains 1      // true
[1; 2] |> List.exists (fun x -> x >= 2)  // true
[1; 2] |> List.forall (fun x -> x >= 2)  // false

Select elements

↓ Which elements \ Search →
By size
By predicate

All those found

filter f

First ignored

skip n

skipWhile f

First found

take n

takeWhile f

truncate n

☝ Notes:

  • skip, take vs truncate when n > collection's size

    • skip, take: 💣 exception

    • truncate: empty collections w/o exception

  • Alternative for Array: Range arr[2..5]

Map elements

Functions taking as input:

  • A mapping function f (a.k.a. mapper)

  • A collection of type ~~~~ 'T with ~~~~ meaning Array, List, or Seq

Function
Mapping f
Returns
How many elements?

map

       'T -> 'U      

'U ~~~~

As many in than out

mapi

int -> 'T -> 'U      

'U ~~~~

As many in than out

collect

       'T -> 'U ~~~~ 

'U ~~~~

flatMap

choose

       'T -> 'U option

'U ~~~~

Less

pick

       'T -> 'U option

'U     

1 (the first matching) or 💣

tryPick

       'T -> 'U option

'U option

1 (the first matching)

map vs mapi

mapi ≡ map with index

The difference is on the f mapping parameter:

  • map: 'T -> 'U

  • mapi: int -> 'T -> 'U → the additional int parameter is the item index

["A"; "B"]
|> List.mapi (fun i x -> $"{i+1}. {x}")
// ["1. A"; "2. B"]

Alternative to mapi

Apart from map and iter, no xxx function has a xxxi variant.

💡 Use indexed to obtain elements with their index

let isOk (i, x) = i >= 1 && x <= "C"

["A"; "B"; "C"; "D"]
|> List.indexed       // [ (0, "A"); (1, "B"); (2, "C"); (3, "D") ]
|> List.filter isOk   //           [ (1, "B"); (2, "C") ]
|> List.map snd       //               [ "B" ; "C" ]

map vs iter

iter looks like map with

  • no mapping: 'T -> unit vs 'T -> 'U

  • no output: unit vs 'U list

But iter is used for a different use case: → to trigger an action, a side-effect, for each item

Example: print the item to the console

["A"; "B"; "C"] |> List.iteri (fun i x -> printfn $"Item #{i}: {x}")
// Item #0: A
// Item #1: B
// Item #2: C

choose, pick, tryPick

Signatures:

• choose : mapper: ('T -> 'U option) -> items: 'T ~~~~ -> 'U ~~~~ • pick : mapper: ('T -> 'U option) -> items: 'T ~~~~ -> 'U • tryPick : mapper: ('T -> 'U option) -> items: 'T ~~~~ -> 'U option

Notes:

  • The mapper may return None for some items not mappable (or just ignored)

  • ~~~~ stands for the collection type: Array, List, or Seq

Different use cases:

  • choose to get all the mappable items mapped

  • pick or tryPick to get the first one

When no items are mappable:

  • choose returns an empty collection

  • tryPick returns None

  • pick raises a KeyNotFoundException 💣

Examples:

let tryParseInt (s: string) =
    match System.Int32.TryParse(s) with
    | true,  i -> Some i
    | false, _ -> None

["1"; "2"; "?"] |> List.choose tryParseInt   // [1; 2]
["1"; "2"; "?"] |> List.pick tryParseInt     // 1
["1"; "2"; "?"] |> List.tryPick tryParseInt  // Some 1

Analogies:

choose f ≃ • collect (f >> Option.to{Collection}) • (filter (f >> Option.isSome)) >> (map (f >> Option.value))

(try)pick f ≃ • (try)find(f >> Option.isSome)) >> f • choose f >> (try)head

Aggregate

Specialized aggregate functions

Direct
By projection
Mapping
Constraint

×

countBy

×

max

maxBy

comparison

min

minBy

comparison

sum

sumBy

📍

average

averageBy

📍

Warning

💣 ArgumentException if the collection is empty.

CountBy

Uses a projection f: 'T -> 'U to compute a "key" for each item Returns all the keys with the number of items having this key

let words = [ "hello"; "world"; "fsharp"; "is"; "awesome" ]
let wordCountByLength = words |> List.countBy String.length |> List.sortBy fst
//val wordCountByLength: (int * int) list = [(2, 1); (5, 2); (6, 1); (7, 1)]

💡 Useful to determine duplicates:

let findDuplicates items =
    items
    |> List.countBy id
    |> List.choose (fun (item, count) -> if count > 1 then Some item else None)

let t = findDuplicates [1; 2; 3; 4; 5; 1; 2; 3]
// val t: int list = [1; 2; 3]

Max(By), Min(By)

type N = One | Two | Three | Four | Five

let max = List.max [ One; Two; Three; Four; Five ]
// val max: N = Five

let maxText = List.maxBy string [ One; Two; Three; Four; Five ]
// val maxText: N = Two

☝️ Notes:

  • Unions are IComparable by default → N follows the comparison constraint.

  • maxBy and minBy perform no mapping: → See the returned value in the example: Two (N), ≠ "Two" (string)

Sum(By), Average(By)

let sumKO = List.sum [ (1,"a"); (2,"b"); (3,"c") ]
//                      ~~~~~ 💥 Error FS0001
// Expecting a type supporting the operator 'get_Zero' but given a tuple type

let sum = List.sumBy fst [ (1,"a"); (2,"b"); (3,"c") ]
// val sum: int = 6 

☝️ Notes:

  • The error FS0001 at line 1 reveals the Monoid constraint (see below) that must be satisfied by:

    • The item type 'T for sum and average,

    • The projection return type 'U for sumBy and averageBy.

  • sumBy and averageBy perform a mapping → See the returned type in the example: int ≠ item type: int * string

  • sum ≡ sumBy id average ≡ averageBy id

Monoid constraint

To satisfy the monoid constraint, a type must have:

  • A Zero static read-only property

  • An overload of the + operator

Example of a custom type with these members:

type Point = Point of X:int * Y:int with
    static member Zero = Point (0, 0)
    static member (+) (Point (ax, ay), Point (bx, by)) = Point (ax + bx, ay + by)

let addition = (Point (1, 1)) + (Point (2, 2))
// val addition : Point = Point (3, 3)

let sum = [1..3] |> List.sumBy (fun i -> Point (i, -i))
// val sum : Point = Point (6, -6)

Versatile aggregate functions

• fold (f: 'U -> 'T -> 'U) (seed: 'U) items • foldBack (f: 'T -> 'U -> 'U) items (seed: 'U) • reduce (f: 'T -> 'T -> 'T) items • reduceBack (f: 'T -> 'T -> 'T) items

f takes 2 parameters: an "accumulator" acc and the current element x → For foldBack, the parameters are in a reversed order: x acc

xxxBack vs xxx:

  • Iterates from last to first element

  • Parameters seed and items reversed (for foldBack)

reduce(Back) vs fold(Back):

  • reduce(Back) uses the first item as the seed and performs no mapping (see 'T -> 'T vs 'T -> 'U)

  • reduce(Back) fails if the collection is empty 💣

Examples:

["a";"b";"c"] |> List.reduce (+)  // "abc"
[ 1; 2; 3 ] |> List.reduce ( * )  // 6

[1;2;3;4] |> List.reduce     (fun acc x -> 10 * acc + x)  // 1234
[1;2;3;4] |> List.reduceBack (fun x acc -> 10 * acc + x)  // 4321

("all:", [1;2;3;4]) ||> List.fold     (fun acc x -> $"{acc}{x}")  // "all:1234"
([1;2;3;4], "rev:") ||> List.foldBack (fun x acc -> $"{acc}{x}")  // "rev:4321"

Fold(Back) versatility

We could write almost all functions with fold or foldBack (performance aside)

// collect function using fold
let collect f list = List.fold (fun acc x -> acc @ f x) [] list

let test1 = [1; 2; 3] |> collect (fun x -> [x; x])  // [1; 1; 2; 2; 3; 3]

// filter function using foldBack
let filter f list = List.foldBack (fun x acc -> if f x then x :: acc else acc) list []

let test2 = [1; 2; 3; 4; 5] |> filter ((=) 2)  // [2]

// map function using foldBack
let map f list = List.foldBack (fun x acc -> f x :: acc) list []

let test3 = [1; 2; 3; 4; 5] |> map (~-)  // [-1; -2; -3; -4; -5]

Change the order of elements

Operation
Direct
Mapping

Inversion

rev list

×

Sort asc

sort list

sortBy f list

Sort desc

sortDescending list

sortDescendingBy f list

Sort custom

sortWith comparer list

×

[1..5] |> List.rev // [5; 4; 3; 2; 1]
[2; 4; 1; 3; 5] |> List.sort // [1..5]
["b1"; "c3"; "a2"] |> List.sortBy (fun x -> x[0]) // ["a2"; "b1"; "c3"] because a < b < c
["b1"; "c3"; "a2"] |> List.sortBy (fun x -> x[1]) // ["b1"; "a2"; "c3"] because 1 < 2 < 3

Separate

💡 Elements are divided into groups

Operation
Result (; omitted)
Remark

[1..10]

[ 1 2 3 4 5 6 7 8 9 10 ]

length = 10

chunkBySize 3

[[1 2 3] [4 5 6] [7 8 9] [10]]

forall: length <= 3

splitInto 3

[[1 2 3 4] [5 6 7] [8 9 10]]

length <= 3

splitAt 3

([1 2 3],[4 5 6 7 8 9 10])

Tuple ❗

☝️ Notice how splitInto n distributes the items equally, from left to right:

let a7  = [1..7]  |> List.splitInto 3  // [[1; 2; 3]; [4; 5]; [6; 7]]
let a8  = [1..8]  |> List.splitInto 3  // [[1; 2; 3]; [4; 5; 6]; [7; 8]]
let a9  = [1..9]  |> List.splitInto 3  // [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]
let a10 = [1..10] |> List.splitInto 3  // [[1; 2; 3; 4]; [5; 6; 7]; [8; 9; 10]]
Size in  →  Sizes out
7        →  3  2  2
8        →  3  3  2
9        →  3  3  3
10       →  4  3  3

Group items

By size

💡 Items can be duplicated into different groups

Operation
Result (' and ; omitted)
Remark

[1..5]

[ 1 2 3 4 5 ]

pairwise

[(1,2) (2,3) (3,4) (4,5)]

Tuple ❗

windowed 2

[[1 2] [2 3] [3 4] [4 5]]

Array of arrays of 2 items

windowed 3

[[1 2 3] [2 3 4] [3 4 5]]

Array of arrays of 3 items

By criteria

Operation
Criteria
Result

partition

predicate: 'T -> bool

('T list * 'T list)

→ 1 pair ([OKs], [KOs])

groupBy

projection: 'T -> 'K

('K * 'T list) list

→ N tuples [(key, [related items])]

let isOdd i = (i % 2 = 1)
[1..10] |> List.partition isOdd // (        [1; 3; 5; 7; 9] ,         [2; 4; 6; 8; 10]  )
[1..10] |> List.groupBy isOdd   // [ (true, [1; 3; 5; 7; 9]); (false, [2; 4; 6; 8; 10]) ]

let firstLetter (s: string) = s.[0]
["apple"; "alice"; "bob"; "carrot"] |> List.groupBy firstLetter
// [('a', ["apple"; "alice"]); ('b', ["bob"]); ('c', ["carrot"])]

Change collection type

Your choice: Dest.ofSource or Source.toDest

From \ to

Array

List

Seq

Array

×

List.ofArray

Seq.ofArray

×

Array.toList

Array.toSeq

List

Array.ofList

×

Seq.ofList

List.toArray

×

List.toSeq

Seq

Array.ofSeq

List.ofSeq

×

Seq.toArray

Seq.toList

×

Functions vs comprehension

The functions of List/Array/Seq can often be replaced by a comprehension, more versatile:

let list = [ 0..99 ]

list |> List.map f                   <->  [ for x in list do f x ]
list |> List.filter p                <->  [ for x in list do if p x then x ]
list |> List.filter p |> List.map f  <->  [ for x in list do if p x then f x ]
list |> List.collect g               <->  [ for x in list do yield! g x ]

Additional resources

Documentation FSharp.Core 👍

PreviousTypesNextDedicated functions

Last updated 1 month ago

Was this helpful?

Array module
List module
Map module
Seq module
Set module