Skip to content

Latest commit

Β 

History

History
687 lines (478 loc) Β· 17.9 KB

functions.md

File metadata and controls

687 lines (478 loc) Β· 17.9 KB

Useful functions

It's useful for cases where you need a lambda like fun x -> x:

[[1;2]; [3]] |> List.collect (fun x -> x)  // [1; 2; 3]
[[1;2]; [3]] |> List.collect id            // [1; 2; 3]

Mapping functions

map (Array, List, Seq)

Converts all the items in a collection from one shape to another shape. Always returns the same number of items in the output collection as were passed in.

// [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
[1 .. 10] |> List.map (fun n -> n * 2)

type Person = { Name : string; Town : string }

let persons =
    [
        { Name = "Isaak"; Town = "London" }
        { Name = "Sara"; Town = "Birmingham" }
        { Name = "Tim"; Town = "London" }
        { Name = "Michelle"; Town = "Manchester" }
    ]

// ["London"; "Birmingham"; "London"; "Manchester"]
persons |> List.map (fun person -> person.Town)

map2, map3 (Array, List, Seq)

map2 and map3 are variations of map that take multiple lists. The collections must have equal lengths, except for Seq.map2 and Seq.map3 where extra elements are ignored.

let list1 = [1; 2; 3]
let list2 = [4; 5; 6]
let list3 = [7; 8; 9]

// [5; 7; 9]
(list1, list2) ||> List.map2 (fun x y -> x + y)

// [12; 15; 18]
(list1, list2, list3) |||> List.map3 (fun x y z -> x + y + z) 

mapi, mapi2 (Array, List, Seq)

mapi and mapi2 - in addition to the element, the function needs to be passed the index of each element. The only difference mapi2 and mapi is that mapi2 works with two collections.

let list1 = [9; 12; 53; 24; 35]

// [(0, 9); (1, 12); (2, 53); (3, 24); (4, 35)]
list1 |> List.mapi (fun x i -> (x, i))

// [9; 13; 55; 27; 39]
list1 |> List.mapi (fun x i -> x + i)

let list1 = [9; 12; 3]
let list2 = [24; 5; 2]

// [0; 17; 10]
(list1, list2) ||> List.mapi2 (fun i x y -> (x + y) * i) 

indexed (Array, List, Seq)

Returns a new collection whose elements are the corresponding elements of the input paired with the index (from 0) of each element.

let list1 = [23; 5; 12]

// [(0, 23); (1, 5); (2, 12)]
let result = list1 |> Array.indexed

iter, iter2, iteri, iteri2 (Array, List, Seq)

iter is the same as a for loop, the function that you pass in must return unit.

let list1 = [1; 2; 3]
let list2 = [4; 5; 6]

// Prints: 1; 2; 3; 
list1 |> List.iter (fun x -> printf "%d; " x)

// Prints: (1 4); (2 5); (3 6);
(list1, list2) ||> List.iter2 (fun x y -> printf "(%d %d); " x y)

// Prints: ([0] = 1); ([1] = 2); ([2] = 3);
list1 |> List.iteri (fun i x -> printf "([%d] = %d); " i x)

// Prints: ([0] = 1 4); ([1] = 2 5); ([2] = 3 6);
(list1, list2) ||> List.iteri2 (fun i x y -> printf "([%d] = %d %d); " i x y) 

collect (Array, List, Seq)

collect runs a specified function on each element and then collects the elements generated by the function and combines them into a new collection.

// [0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
let list1 = [1; 5; 10]
let list2 = [1; 2; 3]

list1 |> List.collect (fun elem -> [0 .. elem])

// [1; 2; 3; 2; 4; 6; 3; 6; 9]
list2 |> List.collect (fun x -> [for i in 1..3 -> x * i])

// Example 3
type Customer =
    {
        Id: int
        OrderId: int list
    }

let c1 = { Id = 1; OrderId = [1; 2]}
let c2 = { Id = 2; OrderId = [43]}
let c3 = { Id = 5; OrderId = [39; 56]}
let customers = [c1; c2; c3]

// [1; 2; 43; 39; 56]
let orders = customers |> List.collect(fun c -> c.OrderId)

pairwise (Array, List, Seq)

pairwise takes a collection and returns a new list of tuple pairs of the original adjacent items.

let list1 = [1; 30; 12; 20]

//  [(1, 30); (30, 12); (12, 20)]
list1 |> List.pairwise

// [31.0; 11.0; 21.0]
[ DateTime(2010,5,1)
  DateTime(2010,6,1)
  DateTime(2010,6,12)
  DateTime(2010,7,3) ]
|> List.pairwise
|> List.map(fun (a, b) -> b - a)
|> List.map(fun time -> time.TotalDays)

windowed (Array, List, Seq)

Returns a list of sliding windows containing elements drawn from the input collection. Each window is returned as a fresh collection. Unlike pairwise the windows are collections, not tuples.

// [['a'; 'b'; 'c']; ['b'; 'c'; 'd']; ['c'; 'd'; 'e']]
['a'..'e'] |> List.windowed 3

Grouping functions

groupBy (Array, List, Seq) == GroupBy() in LINQ

groupBy works exactly as the LINQ version does. The output is a collection of simple tuples. The first element of the tuple is the key, and the second element is the collection of items in that group.

type Person =
    {
        Name: string
        Town: string
    }

let persons =
    [ { Name = "Isaak"; Town = "London" }
      { Name = "Sara"; Town = "Birnmingham" }
      { Name = "Tim"; Town = "London" }
      { Name = "Michelle"; Town = "Manchester" } ]

// [("London", [{ Name = "Isaak"; Town = "London" }; { Name = "Tim"; Town = "London" }]);
//  ("Birnmingham", [{ Name = "Sara"; Town = "Birnmingham" }]);
//  ("Manchester", [{ Name = "Michelle"; Town = "Manchester" }])]
persons |> List.groupBy (fun person -> person.Town)

countBy (Array, List, Seq)

A useful derivative of groupBy is countBy. This has a similar signature, but instead of returning the items in the group, it returns the number of items in each group.

type Person = { Name: string; Town: string }

let persons =
    [
        { Name = "Isaak"; Town = "London" }
        { Name = "Sara"; Town = "Birnmingham" }
        { Name = "Tim"; Town = "London" }
        { Name = "Michelle"; Town = "Manchester" }
    ]

// [("London", 2); ("Birnmingham", 1); ("Manchester", 1)]
persons |> List.countBy (fun person -> person.Town)

partition (Array, List)

partition use predicate and a collection; it returns two collections, partitioned based on the predicate:

// Tupled result in two lists
let londonPersons, otherPersons =
    persons |> List.partition(fun p -> p.Town = "London")

If there are no matches for either half of the split, an empty collection is returned for that half.

chunkBySize (Array, List, Seq)

chunkBySize groups elements into arrays (chunks) of a given size.

let list1 = [33; 5; 16]

// int list list = [[33; 5]; [16]]
let chunkedLst = list1 |> List.chunkBySize 2

splitAt (Array, List)

splitAt splits an Array (List) into two parts at the index you specify. The first part ends just before the element at the given index; the second part starts with the element at the given index.

let xs = [| 1; 2; 3; 4; 5 |]

let left1, right1 = xs |> Array.splitAt 0   // [||] and [|1; 2; 3; 4; 5|]
let left2, right2 = xs |> Array.splitAt 1   // [|1|] and [|2; 3; 4; 5|]
let left3, right3 = xs |> Array.splitAt 5   // [|1; 2; 3; 4; 5|] and [||]
let left4, right4 = xs |> Array.splitAt 6   // InvalidOperationException

splitInto (Array, List, Seq)

Splits the input collection into at most count chunks.

// [[1; 2; 3; 4]; [5; 6; 7]; [8; 9; 10]]
// note that the first chunk has four elements
[1..10] |> List.splitInto 3

// [[1; 2; 3]; [4; 5; 6]; [7; 8]; [9; 10]]
[1..10] |> List.splitInto 4

// [[1; 2]; [3; 4]; [5; 6]; [7; 8]; [9]; [10]]
[1..10] |> List.splitInto 6

Aggregate functions

Aggregate functions take a collection of items and merge them into a smaller collection of items (often just one).

sum, average, min, max (Array, List, Seq)

All of these functions are specialized versions of a more generalized function fold.

let numbers = [1.0 .. 10.0]

let total = numbers |> List.sum         // 55.0
let average = numbers |> List.average   // 5.5
let max = numbers |> List.max           // 10.0
let min = numbers |> List.min           // 1.0

Miscellaneous functions

find (Array, List, Seq) == Single() in LINQ

find - finds the first element that matches a given condition.

let isDivisibleBy number elem = elem % number = 0

let input = [1 .. 10]

input |> List.find (isDivisibleBy 5)    // 5
input |> List.find (isDivisibleBy 11)   // KeyNotFoundException

findBack (Array, List, Seq)

let isDivisibleBy number elem = elem % number = 0

let input = [1 .. 10]

input |> List.findBack (isDivisibleBy 4)    // 8
input |> List.findBack (isDivisibleBy 11)   // KeyNotFoundException

findIndex (Array, List, Seq)

let isDivisibleBy number elem = elem % number = 0

let input = [1 .. 10]

input |> List.findIndex (isDivisibleBy 5)   // 4
input |> List.findIndex (isDivisibleBy 11)  // KeyNotFoundException

findIndexBack (Array, List, Seq)

let isDivisibleBy number elem = elem % number = 0

let input = [1..10]

input |> List.findIndexBack (isDivisibleBy 3)   // 8
input |> List.findIndexBack (isDivisibleBy 11)  // KeyNotFoundException

head, last, tail, item (Array, List, Seq)

Returns the first, last and all-but-first items in the collection.

let input = [15..22]

input |> List.head    // 15
input |> List.last    // 22
input |> List.tail    // [16; 17; 18; 19; 20; 21; 22]

item (Array, List, Seq)

Gets the element at a given index.

let input = [1..7]

input |> List.item 5      // 6
input |> List.item 8      // ArgumentException

take (Array, List, Seq)

Returns the elements of the collection up to a specified count.

let input = [1..10]

input |> List.take 5        // [1; 2; 3; 4; 5]
input |> List.take 11       // InvalidOperationException

truncate (Array, List, Seq)

Returns a collection that when enumerated returns at most N elements.

let input = [1..10]

input |> List.truncate 5    // [1; 2; 3; 4; 5]
input |> List.truncate 11   // [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

takeWhile (Array, List, Seq)

takeWhile returns each item in a new collection until it reaches an item that does not meet the predicate.

let input = [1..10]

input |> List.takeWhile (fun x -> x / 7 = 0)   // [1; 2; 3; 4; 5; 6]
input |> List.takeWhile (fun x -> x / 17 = 0)     // [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

skip (Array, List, Seq)

Returns a collection that skips N elements of the underlying sequence and then yields the remaining elements.

let input = [ for i in 1 .. 10 -> i * i ]  // [1; 4; 9; 16; 25; 36; 49; 64; 81; 100]

input |> List.skip 5                       // [36; 49; 64; 81; 100]
input |> List.skip 11                      // ArgumentException

skipWhile (Array, List, Seq)

Returns a collection, when iterated, skips elements of the underlying array (list, seq) while the given predicate returns true, and then yields the remaining elements.

let mySeq = seq { for i in 1 .. 10 -> i * i }    // 1 4 9 16 25 36 49 64 81 100

mySeq |> Seq.skipWhile (fun elem -> elem < 10)   // 16 25 36 49 64 81 100
mySeq |> Seq.skipWhile (fun elem -> elem < 101)  // Empty seq

exists (Array, List, Seq)

Tests if any element of the collection satisfies the given predicate.

let inputs = [0..3]

inputs |> List.exists (fun elem -> elem = 3)      // true
inputs |> List.exists (fun elem -> elem = 10)     // false

exists2 (Array, List, Seq)

Tests if any pair of corresponding elements of the collections satisfies the given predicate. The collections must have equal lengths, except for Seq where extra elements are ignored.

let list1to5 = [1 .. 5]           // [1; 2; 3; 4; 5]
let list0to4 = [0 .. 4]           // [0; 1; 2; 3; 4]
let list5to1 = [5 .. -1 .. 1]     // [5; 4; 3; 2; 1]
let list6to1 = [6 .. -1 .. 1]     // [6; 5; 4; 3; 2; 1]

(list1to5, list5to1) ||> List.exists2 (fun i1 i2 -> i1 = i2)    // true
(list1to5, list0to4) ||> List.exists2 (fun i1 i2 -> i1 = i2)    // false
(list1to5, list6to1) ||> List.exists2 (fun i1 i2 -> i1 = i2)    // ArgumentException

forall (Array, List, Seq)

Tests if all elements of the collection satisfy the given predicate.

let inputs = [2; 4; 6; 8; 10]

inputs |> List.forall (fun i -> i % 2 = 0)    // true
inputs |> List.forall (fun i -> i % 2 = 0)    // false

forall2 (Array, List, Seq)

Returns true if all corresponding elements of the collection satisfy the given predicate pairwise. The collections must have equal lengths, except for Seq where extra elements are ignored.

let lst1 = [0; 1; 2]
let lst2 = [0; 1; 2]
let lst3 = [2; 1; 0]
let lst4 = [0; 1; 2; 3]

(lst1, lst2) ||> List.forall2 (fun i1 i2 -> i1 = i2)    // true
(lst1, lst3) ||> List.forall2 (fun i1 i2 -> i1 = i2)    // false
(lst1, lst4) ||> List.forall2 (fun i1 i2 -> i1 = i2)    // ArgumentException

contains (Array, List, Seq)

Returns true if a collection contains an equal value:

let rushSet = ["Dirk"; "Lerxst"; "Pratt"]
let gotSet = rushSet |> List.contains "Lerxst"      // true

filter, where (Array, List, Seq)

Returns a new collection containing only the elements of the collection for which the given predicate returns true.

let data =
    [("Cats",4)
     ("Tiger",5)
     ("Mice",3)
     ("Elephants",2) ]

// [("Cats", 4); ("Mice", 3)]
data |> List.filter (fun (nm, x) -> nm.Length <= 4)
data |> List.where (fun (nm, x) -> nm.Length <= 4)

length (Array, List, Seq)

Returns the length of the collection.

[ 1 .. 100 ] |> List.length         // 100
[ ] |> List.length                  // 0
[ 1 .. 2 .. 100 ] |> List.length    // 50

distinctBy (Array, List, Seq)

Returns a collection that contains no duplicate entries according to the generic hash and equality comparisons on the keys returned by the given key-generating function.

let inputs = [-5 .. 10]

// [-5; -4; -3; -2; -1; 0; 6; 7; 8; 9; 10]
inputs  |> List.distinctBy (fun i -> abs i)

distinct (Array, List, Seq) == Distinct() in LINQ

Returns a collection that contains no duplicate entries according to generic hash and equality comparisons on the entries.

[1; 3; 9; 4; 3; 1] |> List.distinct    // [1; 3; 9; 4]
[1; 1; 1; 1; 1; 1] |> List.distinct     // [1]
[ ] |> List.distinct                    // error FS0030: Value restriction

sortBy (Array, List, Seq) == OrderBy() in LINQ

Sorts the given collection using keys given by the given projection. Keys are compared using Operators.compare.

[1; 4; 8; -2; 5] |> List.sortBy (fun x -> abs x)    // [1; -2; 4; 5; 8]

sort (Array, List, Seq)

Sorts the given list using Operators.compare.

[1; 4; 8; -2; 5] |> List.sort    // [-2; 1; 4; 5; 8]

sortByDescending (Array, List, Seq)

[-3..3] |> List.sortByDescending (fun x -> abs x)   // [-3; 3; -2; 2; -1; 1; 0]

sortDescending (Array, List, Seq)

[0..5] |> List.sortDescending    // [5; 4; 3; 2; 1; 0]

sortWith (Array, List, Seq)

Sorts the given collection using the given comparison function.

let lst = ["<>"; "&"; "&&"; "&&&"; "<"; ">"; "|"; "||"; "|||"]

let sortFunction (str1: string) (str2: string) =
    if (str1.Length > str2.Length) then
        1
    else
        -1

// ["|"; ">"; "<"; "&"; "||"; "&&"; "<>"; "|||"; "&&&"]
lst |> List.sortWith sortFunction

Array, List and Seq functions

allPairs (Array, List, Seq)

Takes 2 arrays (list, seq), and returns all possible pairs of elements.

let arr1 = [| 0; 1 |]
let arr2 = [| 4; 5 |]

(arr1, arr2) ||> Array.allPairs arr1 arr2    // [|(0, 4); (0, 5); (1, 4); (1, 5)|]

append (Array, List, Seq)

Combines 2 arrays (list, seq).

let list1 = [33; 5; 16]
let list2 = [42; 23; 18]

List.append list1 list2     // [33; 5; 16; 42; 23; 18]

averageBy (Array, List, Seq)

averageBy take a function as a parameter, and this function's results are used to calculate the values for the average.

// val avg1 : float = 2.0
[1..3] |> List.averageBy (fun elem -> float elem)

// val avg2 : float = 4.666666667
[| 1..3 |] |> Array.averageBy (fun elem -> float (elem * elem))

Seq.cache

Seq.cache creates a stored version of a sequence. Use Seq.cache to avoid reevaluation of a sequence, or when you have multiple threads that use a sequence, but you must make sure that each element is acted upon only one time. When you have a sequence that is being used by multiple threads, you can have one thread that enumerates and computes the values for the original sequence, and remaining threads can use the cached sequence.

choose (Array, List, Seq)

choose enables you to transform and select elements at the same time.

let list1 = [33; 5; 16]

// [34; 6; 17]
list1 |> List.choose (fun elem -> Some(elem + 1)) 

compareWith (Array, List, Seq)

Compare two arrays (lists, seq) by using the compareWith function. The function compares successive elements in turn, and stops when it encounters the first unequal pair. Any additional elements do not contribute to the comparison.

let sq1 = seq { 1; 2; 4; 5; 7 }
let sq2 = seq { 1; 2; 3; 5; 8 }
let sq3 = seq { 1; 3; 3; 5; 2 }

let compareSeq seq1 seq2 =
    (seq1, seq2 ||> Seq.compareWith (fun e1 e2 ->
        if e1 > e2 then 1
        elif e1 < e2 then -1
        else 0)

let compareResult1 = compareSeq sq1 sq2     // int = 1
let compareResult2 = compareSeq sq2 sq3     // int = -1

concat (Array, List, Seq)

concat is used to join any number of arrays (lists, seq).

// int list = [1; 2; 3; 4; 5; 6; 7; 8; 9]
List.concat [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]]

Array.copy

Creates a new array that contains elements that are copied from an existing array. The copy is a shallow copy, which means that if the element type is a reference type, only the reference is copied, not the underlying object.

let firstArray : StringBuilder array = Array.init 3 (fun index -> new StringBuilder(""))
let secondArray = Array.copy firstArray

firstArray.[0] <- new StringBuilder("Test1")
firstArray[0] <- new StringBuilder("Test1") // F# 6

firstArray.[1].Insert(0, "Test2") |> ignore
firstArray[1].Insert(0, "Test2") |> ignore // F# 6