F# Collections 1

Array

Arrays are fixed-size, zero-based, mutable collections of consecutive data elements that are all of the same type.

Create array

// 
let array1 = [| 1; 2; 3 |]

// Put each element on a separate line, in which case the semicolon separator is optional.
let array1 =
    [|
        1
        2
        3
     |]

// The type of the array elements is inferred from the literals used and must be consistent. 
//  The following code causes an error because 1.0 is a float and 2 and 3 are integers.
// Causes an error.
// let array2 = [| 1.0; 2; 3 |]


// Use sequence expressions to create arrays.
let array3 = [| for i in 1 .. 10 -> i * i |]

// use Array.zeroCreate
let arrayOfTenZeroes : int array = Array.zeroCreate 10

// use Array.empty
let myEmptyArray = Array.empty
printfn "Length of empty array: %d" myEmptyArray.Length
// Length of empty array: 0

// use Array.create
printfn "Array of floats set to 5.0: %A" (Array.create 10 5.0)
//  Area of floats set to 5.0: [|5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0; 5.0|]

// Array.init
printfn "Array of squares: %A" (Array.init 10 (fun index -> index * index))
// Array of squares: [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]

Copy

open System.Text

let firstArray : StringBuilder array = Array.init 3 (fun index -> new StringBuilder(""))
let secondArray = Array.copy firstArray
// Reset an element of the first array to a new value.
firstArray[0] <- new StringBuilder("Test1")
// Change an element of the first array.
firstArray[1].Insert(0, "Test2") |> ignore
printfn "%A" firstArray
printfn "%A" secondArray
// output
// [|Test1; Test2; |]
// [|; Test2; |]

Access

// Accesses elements from 0 to 2.
array1[0..2]

// Accesses elements from the beginning of the array to 2.
array1[..2]

// Accesses elements from 2 to the end of the array.
array1[2..]

Functions

// Array.sub 
let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2
// output
// [|5; 6; 7; 8; 9; 10; 11; 12; 13; 14|]


// Array.append creates a new array by combining two existing arrays.
printfn "%A" (Array.append [| 1; 2; 3|] [| 4; 5; 6|])
// output
// [|1; 2; 3; 4; 5; 6|]




// Array.choose selects elements of an array to include in a new array. 
printfn "%A" (Array.choose (fun elem -> if elem % 2 = 0 then
                                            Some(float (elem*elem - 1))
                                        else
                                            None) [| 1 .. 10 |])
// output
// [|3.0; 15.0; 35.0; 63.0; 99.0|]


// Array.collect runs a specified function on each array element of an existing array and then collects the elements generated by the function and combines them into a new array. 
printfn "%A" (Array.collect (fun elem -> [| 0 .. elem |]) [| 1; 5; 10|])
// output
// [|0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]


Array.concat takes a sequence of arrays and combines them into a single array. The following code demonstrates Array.concat.

F#

Copy
Array.concat [ [|0..3|] ; [|4|] ]
//output [|0; 1; 2; 3; 4|]

Array.concat [| [|0..3|] ; [|4|] |]
//output [|0; 1; 2; 3; 4|]



// Array.filter takes a Boolean condition function and generates a new array that contains only those elements from the input array for which the condition is true.
printfn "%A" (Array.filter (fun elem -> elem % 2 = 0) [| 1 .. 10|])
// The output
// [|2; 4; 6; 8; 10|]


// Array.rev generates a new array by reversing the order of an existing array. 
let stringReverse (s: string) =
    System.String(Array.rev (s.ToCharArray()))

printfn "%A" (stringReverse("!dlrow olleH"))
// The output 
// "Hello world!"

// You can easily combine functions in the array module that transform arrays by using the pipeline operator (|>)
[| 1 .. 10 |]
|> Array.filter (fun elem -> elem % 2 = 0)
|> Array.choose (fun elem -> if (elem <> 8) then Some(elem*elem) else None)
|> Array.rev
|> printfn "%A"
// output
// [|100; 36; 16; 4|]

Multidimensional arrays

let my2DArray = array2D [ [ 1; 0]; [0; 1] ]
let arrayOfArrays = [| [| 1.0; 0.0 |]; [|0.0; 1.0 |] |]
let twoDimensionalArray = Array2D.init 2 2 (fun i j -> arrayOfArrays[i][j])

Slicing

// Get rows 1 to N from an NxM matrix (returns a matrix):
matrix[1.., *]

// Get rows 1 to 3 from a matrix (returns a matrix):
matrix[1..3, *]

// Get columns 1 to 3 from a matrix (returns a matrix):
matrix[*, 1..3]

// Get a 3x3 submatrix:
matrix[1..3, 1..3]

// Get row 3 from a matrix as a vector:
matrix[3, *]

// Get column 3 from a matrix as a vector:
matrix[*, 3]

Matrix & slicing


type Matrix<'T>(N: int, M: int) =
    let internalArray = Array2D.zeroCreate<'T> N M

    member this.Item
        with get(a: int, b: int) = internalArray[a, b]
        and set(a: int, b: int) (value:'T) = internalArray[a, b] <- value

    member this.GetSlice(rowStart: int option, rowFinish : int option, colStart: int option, colFinish : int option) =
        let rowStart =
            match rowStart with
            | Some(v) -> v
            | None -> 0
        let rowFinish =
            match rowFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(0) - 1
        let colStart =
            match colStart with
            | Some(v) -> v
            | None -> 0
        let colFinish =
            match colFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(1) - 1
        internalArray[rowStart..rowFinish, colStart..colFinish]

    member this.GetSlice(row: int, colStart: int option, colFinish: int option) =
        let colStart =
            match colStart with
            | Some(v) -> v
            | None -> 0
        let colFinish =
            match colFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(1) - 1
        internalArray[row, colStart..colFinish]

    member this.GetSlice(rowStart: int option, rowFinish: int option, col: int) =
        let rowStart =
            match rowStart with
            | Some(v) -> v
            | None -> 0
        let rowFinish =
            match rowFinish with
            | Some(v) -> v
            | None -> internalArray.GetLength(0) - 1
        internalArray[rowStart..rowFinish, col]

module test =
    let generateTestMatrix x y =
        let matrix = new Matrix<float>(3, 3)
        for i in 0..2 do
            for j in 0..2 do
                matrix[i, j] <- float(i) * x - float(j) * y
        matrix

    let test1 = generateTestMatrix 2.3 1.1
    let submatrix = test1[0..1, 0..1]
    printfn $"{submatrix}"

    let firstRow = test1[0,*]
    let secondRow = test1[1,*]
    let firstCol = test1[*,0]
    printfn $"{firstCol}"

// Array.find takes a Boolean function and returns the first element for which 
// the function returns true, or raises a 
// System.Collections.Generic.KeyNotFoundException if no element that satisfies
// the conditionis found. 
// Array.findIndex is like Array.find, except that it returns the index of 
// the element instead of the element itself.

// The following code uses Array.find and Array.findIndex to locate a number that 
// is both a perfect square and perfect cube.

let arrayA = [| 2 .. 100 |]
let delta = 1.0e-10
let isPerfectSquare (x:int) =
    let y = sqrt (float x)
    abs(y - round y) < delta
let isPerfectCube (x:int) =
    let y = System.Math.Pow(float x, 1.0/3.0)
    abs(y - round y) < delta
let element = Array.find (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
let index = Array.findIndex (fun elem -> isPerfectSquare elem && isPerfectCube elem) arrayA
printfn "The first element that is both a square and a cube is %d and its index is %d." element index
// The output is as follows.
// The first element that is both a square and a cube is 64 and its index is 62.

// ----------------------------------------------------------------------------
// Array.tryFind is like Array.find, except that its result is an option type, 
// and it returns None if no element is found. Array.tryFind should be used 
// instead of Array.find when you do not know whether a matching element is 
// in the array. Similarly, Array.tryFindIndex is like Array.findIndex except 
// that the option type is the return value. If no element is found, the option is None.

let delta = 1.0e-10
let isPerfectSquare (x:int) =
    let y = sqrt (float x)
    abs(y - round y) < delta
let isPerfectCube (x:int) =
    let y = System.Math.Pow(float x, 1.0/3.0)
    abs(y - round y) < delta
let lookForCubeAndSquare array1 =
    let result = Array.tryFind (fun elem -> isPerfectSquare elem && isPerfectCube elem) array1
    match result with
    | Some x -> printfn "Found an element: %d" x
    | None -> printfn "Failed to find a matching element."

lookForCubeAndSquare [| 1 .. 10 |]
lookForCubeAndSquare [| 100 .. 1000 |]
lookForCubeAndSquare [| 2 .. 50 |]
// The output is as follows.
// Found an element: 1
// Found an element: 729
// Failed to find a matching element.


// ----------------------------------------------------------------------------
// Use Array.tryPick when you need to transform an element in addition to finding it. 
// The result is the first element for which the function returns the transformed 
// element as an option value, or None if no such element is found.
// The following code shows the use of Array.tryPick. In this case, instead of
// a lambda expression, several local helper functions are defined to simplify the code.

let findPerfectSquareAndCube array1 =
    let delta = 1.0e-10
    let isPerfectSquare (x:int) =
        let y = sqrt (float x)
        abs(y - round y) < delta
    let isPerfectCube (x:int) =
        let y = System.Math.Pow(float x, 1.0/3.0)
        abs(y - round y) < delta
    // intFunction : (float -> float) -> int -> int
    // Allows the use of a floating point function with integers.
    let intFunction function1 number = int (round (function1 (float number)))
    let cubeRoot x = System.Math.Pow(x, 1.0/3.0)
    // testElement: int -> (int * int * int) option
    // Test an element to see whether it is a perfect square and a perfect
    // cube, and, if so, return the element, square root, and cube root
    // as an option value. Otherwise, return None.
    let testElement elem =
        if isPerfectSquare elem && isPerfectCube elem then
            Some(elem, intFunction sqrt elem, intFunction cubeRoot elem)
        else None
    match Array.tryPick testElement array1 with
    | Some (n, sqrt, cuberoot) -> printfn "Found an element %d with square root %d and cube root %d." n sqrt cuberoot
    | None -> printfn "Did not find an element that is both a perfect square and a perfect cube."

findPerfectSquareAndCube [| 1 .. 10 |]
findPerfectSquareAndCube [| 2 .. 100 |]
findPerfectSquareAndCube [| 100 .. 1000 |]
findPerfectSquareAndCube [| 1000 .. 10000 |]
findPerfectSquareAndCube [| 2 .. 50 |]
// The output is as follows.
// Found an element 1 with square root 1 and cube root 1.
// Found an element 64 with square root 8 and cube root 4.
// Found an element 729 with square root 27 and cube root 9.
// Found an element 4096 with square root 64 and cube root 16.
// Did not find an element that is both a perfect square and a perfect cube.

Modify arrays

Array.set sets an element to a specified value. Array.fill sets a range of elements in an array to a specified value. The following code provides an example of Array.fill.

let arrayFill1 = [| 1 .. 25 |]
Array.fill arrayFill1 2 20 0
printfn "%A" arrayFill1

// output
// [|1; 2; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 23; 24; 25|]

Convert to and from other types

Array.ofList creates an array from a list. Array.ofSeq creates an array from a sequence. Array.toList and Array.toSeq convert to these other collection types from the array type.