Matrizes (F#)

Matrizes são coleções mutáveis, de tamanho fixo e base zero de elementos de dados consecutivos que são todos do mesmo tipo.

Criar matrizes

É possível criar matrizes de diversas maneiras. Você pode criar uma pequena matriz listando valores consecutivos entre [| e |] separados por ponto e vírgula, conforme mostrado nos exemplos a seguir.

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

Você também pode colocar cada elemento em uma linha separada. Nesse caso, o separador de ponto e vírgula é opcional.

let array1 =
    [|
        1
        2
        3
     |]

O tipo dos elementos da matriz é inferido dos literais usados e deve ser consistente.

// This is an array of 3 integers.
let array1 = [| 1; 2; 3 |]
// This is an array of a tuple of 3 integers.
let array2 = [| 1, 2, 3 |]

O código a seguir gera um erro porque 3.0 é um float e 1 e 2 são inteiros.

// Causes an error. The 3.0 (float) cannot be converted to integer implicitly.
// let array3 = [| 1; 2; 3.0 |]

O código a seguir também causa um erro porque 1,2 é uma tupla e 3 é um inteiro.

// Causes an error too. The 3 (integer) cannot be converted to tuple implicitly.
// let array4 = [| 1, 2; 3 |]

Você também pode usar expressões de sequência para criar matrizes. Veja a seguir um exemplo que cria uma matriz de quadrados de inteiros de 1 a 10.

let array3 = [| for i in 1 .. 10 -> i * i |]

Para criar uma matriz na qual todos os elementos são inicializados como zero, use Array.zeroCreate.

let arrayOfTenZeroes : int array = Array.zeroCreate 10

Acessar elementos

Você pode acessar elementos da matriz usando colchetes ([ e ]). A sintaxe de ponto original (.[index]) ainda é compatível, mas não é mais recomendada a partir do F# 6.0.

array1[0]

Os índices da matriz começam em 0.

Você também pode acessar elementos da matriz usando a notação de fatia, o que permite especificar um subconjunto da matriz. Exemplos de notação de fatia:

// 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..]

Quando a notação de fatia é usada, uma nova cópia da matriz é criada.

Tipos e módulos de matriz

O tipo de todas as matrizes F# é o tipo System.Array.NET Framework . Portanto, as matrizes F# são compatíveis com todas as funcionalidades disponíveis em System.Array.

O Array módulo dá suporte a operações em matrizes unidimensionais. Os módulos Array2D, Array3De Array4D contêm funções que dão suporte a operações em matrizes de duas, três e quatro dimensões, respectivamente. É possível criar matrizes de classificação maior que quatro usando System.Array.

Funções simples

Array.get obtém um elemento. Array.length obtém o comprimento de uma matriz. Array.set define um elemento com um valor especificado. O exemplo de código a seguir ilustra o uso dessas funções.

let array1 = Array.create 10 ""
for i in 0 .. array1.Length - 1 do
    Array.set array1 i (i.ToString())
for i in 0 .. array1.Length - 1 do
    printf "%s " (Array.get array1 i)

A saída é a seguinte.

0 1 2 3 4 5 6 7 8 9

Funções que criam matrizes

Várias funções criam matrizes sem ter uma matriz existente. Array.empty cria uma nova matriz que não contém nenhum elemento. Array.create cria uma matriz de um tamanho especificado e define todos os elementos para os valores fornecidos. Array.init cria uma matriz, dada uma dimensão e uma função para gerar os elementos. Array.zeroCreate cria uma matriz na qual todos os elementos são inicializados com zero para o tipo da matriz. O seguinte código demonstra essas funções.

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



printfn "Array of floats set to 5.0: %A" (Array.create 10 5.0)


printfn "Array of squares: %A" (Array.init 10 (fun index -> index * index))

let (myZeroArray : float array) = Array.zeroCreate 10

A saída é a seguinte.

Length of empty array: 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 of squares: [|0; 1; 4; 9; 16; 25; 36; 49; 64; 81|]

Array.copy cria uma nova matriz que contém elementos copiados de uma matriz existente. Observe que a cópia é superficial, o que significa que, se o tipo de elemento for um tipo de referência, apenas a referência será copiada, não o objeto subjacente. O exemplo de código a seguir ilustra isso.

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

A saída do código anterior é a seguinte:

[|Test1; Test2; |]
[|; Test2; |]

A cadeia de caracteres Test1 aparece apenas na primeira matriz porque a operação de criação de um novo elemento substitui a referência em firstArray, mas não atinge a referência original a uma cadeia de caracteres vazia que ainda está presente em secondArray. A cadeia de caracteres Test2 aparece em ambas as matrizes porque a Insert operação no tipo System.Text.StringBuilder afeta o objeto subjacente System.Text.StringBuilder, que é referenciado em ambas as matrizes.

Array.sub gera uma nova matriz de um subconjunto de uma matriz. Especifique o subconjunto que fornece o índice inicial e o comprimento. O código a seguir demonstra o uso de Array.sub.

let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2

A saída mostra que a submatriz começa no elemento 5 e contém 10 elementos.

[|5; 6; 7; 8; 9; 10; 11; 12; 13; 14|]

Array.append cria uma nova matriz combinando duas matrizes existentes.

O seguinte código mostra um Array.append.

printfn "%A" (Array.append [| 1; 2; 3|] [| 4; 5; 6|])

A saída do código anterior é a seguinte:

[|1; 2; 3; 4; 5; 6|]

Array.choose seleciona elementos de uma matriz a ser incluída em uma nova matriz. O código a seguir demonstra Array.choose. Observe que o tipo de elemento da matriz não precisa corresponder ao tipo do valor retornado na opção. Neste exemplo, o tipo de elemento é int e a opção é o resultado de uma função polinomial, elem*elem - 1, como um número de ponto flutuante.

printfn "%A" (Array.choose (fun elem -> if elem % 2 = 0 then
                                            Some(float (elem*elem - 1))
                                        else
                                            None) [| 1 .. 10 |])

A saída do código anterior é a seguinte:

[|3.0; 15.0; 35.0; 63.0; 99.0|]

Array.collect executa uma função especificada em cada elemento de matriz de uma matriz existente e, em seguida, coleta os elementos gerados pela função e os combina em uma nova matriz. O código a seguir demonstra Array.collect.

printfn "%A" (Array.collect (fun elem -> [| 0 .. elem |]) [| 1; 5; 10|])

A saída do código anterior é a seguinte:

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

Array.concat usa uma sequência de matrizes e as combina em uma única matriz. O código a seguir demonstra Array.concat.

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

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

Array.filter usa uma função de condição booliana e gera uma nova matriz que contém apenas os elementos da matriz de entrada para a qual a condição é verdadeira. O código a seguir demonstra Array.filter.

printfn "%A" (Array.filter (fun elem -> elem % 2 = 0) [| 1 .. 10|])

A saída do código anterior é a seguinte:

[|2; 4; 6; 8; 10|]

Array.rev gera uma nova matriz invertendo a ordem de uma matriz existente. O código a seguir demonstra Array.rev.

let stringReverse (s: string) =
    System.String(Array.rev (s.ToCharArray()))

printfn "%A" (stringReverse("!dlrow olleH"))

A saída do código anterior é a seguinte:

"Hello world!"

Você pode combinar facilmente funções no módulo de matriz que transformam matrizes usando o operador de pipeline (|>), conforme mostrado no exemplo a seguir.

[| 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"

A saída é

[|100; 36; 16; 4|]

Matrizes multidimensionais

Uma matriz multidimensional pode ser criada, mas não há sintaxe para escrever um literal de matriz multidimensional. Use o operador array2D para criar uma matriz a partir de uma sequência de sequências de elementos da matriz. As sequências podem ser literais de matriz ou lista. Por exemplo, o código a seguir cria uma matriz bidimensional.

let my2DArray = array2D [ [ 1; 0]; [0; 1] ]

Você também pode usar a função Array2D.init para inicializar matrizes de duas dimensões, e funções semelhantes estão disponíveis para matrizes de três e quatro dimensões. Essas funções assumem uma função que é usada para criar os elementos. Para criar uma matriz bidimensional que contém elementos definidos como um valor inicial em vez de especificar uma função, use a função Array2D.create, que também está disponível para matrizes de até quatro dimensões. O exemplo de código a seguir mostra primeiro como criar uma matriz de matrizes que contêm os elementos desejados e, em seguida, usa para gerar a matriz bidimensional Array2D.init desejada.

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

Há suporte para a sintaxe de indexação e de fatiamento de matriz para matrizes de até 4. Quando você especifica um índice em várias dimensões, use vírgulas para separar os índices, conforme ilustrado no exemplo do código a seguir.

twoDimensionalArray[0, 1] <- 1.0

O tipo de uma matriz bidimensional é escrito como <type>[,] (por exemplo, int[,], double[,]) e o tipo de uma matriz tridimensional é escrito como <type>[,,], e assim por diante para matrizes de dimensões maiores.

Somente um subconjunto das funções disponíveis para matrizes unidimensionais também está disponível para matrizes multidimensionais.

Fatiamento de matriz e matrizes multidimensionais

Em uma matriz bidimensional (uma matriz), é possível extrair uma submatriz especificando intervalos e usando um caractere curinga (*) para especificar linhas ou colunas inteiras.

// 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]

Você pode decompor uma matriz multidimensional em submatrizes da mesma dimensão ou menor. Por exemplo, você pode obter um vetor de uma matriz especificando uma única linha ou coluna.

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

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

É possível usar essa sintaxe de fatiamento para tipos que implementam os operadores de acesso de elementos e métodos de sobrecarga GetSlice. Por exemplo, o código a seguir cria um tipo de matriz que encapsula a matriz F# 2D, implementa uma propriedade Item para dar suporte à indexação de matriz e implementa três versões de GetSlice. Se for possível usar esse código como um modelo para seus tipos de matriz, você pode realizar todas as operações de fatiamento descritas nesta seção.

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}"

Funções boolianas nas matrizes

As funções Array.exists e Array.exists2 testam elementos em uma ou duas matrizes, respectivamente. Essas funções assumem uma função de teste e retornam true se houver um elemento (ou par de elementos para Array.exists2) que atenda a condição.

O código a seguir demonstra o uso de Array.exists e Array.exists2. Nesses exemplos, novas funções são criadas com apenas um dos argumentos, nesses casos, o argumento da função.

let allNegative = Array.exists (fun elem -> abs (elem) = elem) >> not
printfn "%A" (allNegative [| -1; -2; -3 |])
printfn "%A" (allNegative [| -10; -1; 5 |])
printfn "%A" (allNegative [| 0 |])


let haveEqualElement = Array.exists2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (haveEqualElement [| 1; 2; 3 |] [| 3; 2; 1|])

A saída do código anterior é a seguinte:

true
false
false
true

Da mesma forma, a função Array.forall testa uma matriz para determinar se cada elemento atende a uma condição booliana. Da mesma forma, a variação Array.forall2 também usa uma função booliana que envolve elementos de duas matrizes de comprimento igual. O código a seguir ilustra o uso dessas funções.

let allPositive = Array.forall (fun elem -> elem > 0)
printfn "%A" (allPositive [| 0; 1; 2; 3 |])
printfn "%A" (allPositive [| 1; 2; 3 |])


let allEqual = Array.forall2 (fun elem1 elem2 -> elem1 = elem2)
printfn "%A" (allEqual [| 1; 2 |] [| 1; 2 |])
printfn "%A" (allEqual [| 1; 2 |] [| 2; 1 |])

A saída para esses exemplos é:

false
true
true
false

Pesquisar matrizes

Array.find usa uma função booliana e retorna o primeiro elemento para o qual a função retorna true ou gera um System.Collections.Generic.KeyNotFoundException se não for encontrado nenhum elemento que atenda a condição. Array.findIndex é igual a Array.find, exceto que ele retorna o índice do elemento em vez do próprio elemento.

O código a seguir usa Array.find e Array.findIndex para localizar um número que é um quadrado perfeito e um cubo perfeito.

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

A saída é a seguinte.

The first element that is both a square and a cube is 64 and its index is 62.

Array.tryFind é igual a Array.find, exceto que seu resultado é um tipo de opção e retorna None se nenhum elemento for encontrado. Array.tryFind deve ser usado em vez de Array.find quando você não sabe se um elemento correspondente está na matriz. Da mesma forma, Array.tryFindIndex é igual a Array.findIndex, exceto que o tipo de opção é o valor retornado. Se nenhum elemento for encontrado, a opção é None.

O código a seguir demonstra o uso de Array.tryFind. Esse código depende do código anterior.

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 |]

A saída é a seguinte.

Found an element: 1
Found an element: 729
Failed to find a matching element.

Use Array.tryPick quando precisar transformar e encontrar um elemento. O resultado é o primeiro elemento para o qual a função retorna o elemento transformado como um valor de opção ou None se nenhum elemento desse tipo for encontrado.

O código a seguir demonstra o uso de Array.tryPick. Nesse caso, em vez de uma expressão lambda, várias funções auxiliares locais são definidas para simplificar o código.

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 |]

A saída é a seguinte.

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.

Executar computações em matrizes

A função Array.average retorna a média de cada elemento em uma matriz. É limitado a tipos de elemento que dão suporte à divisão exata por um inteiro, que inclui tipos de ponto flutuante, mas não tipos integrais. A função Array.averageBy retorna a média dos resultados de chamada de uma função em cada elemento. Para uma matriz de tipo integral, você pode usar Array.averageBy e converter a função de cada elemento em um tipo de ponto flutuante para a computação.

Use Array.max ou Array.min para obter o elemento máximo ou mínimo, se o tipo de elemento for compatível. Da mesma forma, Array.maxBy e Array.minBy permitem que uma função seja primeiro executada, talvez para transformar em um tipo que dê suporte à comparação.

Array.sum adiciona os elementos de uma matriz e Array.sumBy chama uma função em cada elemento e adiciona os resultados juntos.

Para executar uma função em cada elemento em uma matriz sem armazenar os valores retornados, use Array.iter. Para uma função que envolve duas matrizes de comprimento igual, use Array.iter2. Se você também precisar manter uma matriz dos resultados da função, use Array.map ou Array.map2, que opera em duas matrizes por vez.

As variações Array.iteri e Array.iteri2 permitem que o índice do elemento esteja envolvido na computação e também para Array.mapi e Array.mapi2.

As funções Array.fold, Array.foldBack, Array.reduce, Array.reduceBack, Array.scan e Array.scanBack executam algoritmos que incluem todos os elementos de uma matriz. Da mesma forma, as variações Array.fold2 e Array.foldBack2 executam computações em duas matrizes.

Essas funções para executar computações correspondem às funções de mesmo nome no módulo Lista. Para obter exemplos de uso, consulte Listas.

Modificar matrizes

Array.set define um elemento com um valor especificado. Array.fill define um intervalo de elementos em uma matriz como um valor especificado. O código a seguir mostra um exemplo de Array.fill.

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

A saída é a seguinte.

[|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|]

Você pode usar Array.blit para copiar uma subseção de uma matriz para outra matriz.

Converte de e para outros tipos

Array.ofList cria uma matriz a partir de uma lista. Array.ofSeq cria uma matriz de uma sequência. Array.toList e Array.toSeq convertem para esses outros tipos de coleção a partir do tipo de matriz.

Classificar matrizes

Use Array.sort para classificar uma matriz usando a função de comparação genérica. Use Array.sortBy para especificar uma função que gera um valor, conhecido como uma chave, para classificar usando a função de comparação genérica na chave. Use Array.sortWith se você quiser fornecer uma função de comparação personalizada. Array.sort, Array.sortBye Array.sortWith todos retornam a matriz classificada como uma nova matriz. As variações Array.sortInPlace, Array.sortInPlaceBye Array.sortInPlaceWith modificam a matriz existente em vez de retornar uma nova.

Matrizes e tuplas

As funções Array.zip e Array.unzip convertem matrizes de pares de tupla em tuplas de matrizes e vice-versa. Array.zip3 e Array.unzip3 são semelhantes, exceto que funcionam com tuplas de três elementos ou tuplas de três matrizes.

Computações paralelas em matrizes

O módulo Array.Parallel contém funções para executar computações paralelas em matrizes. Este módulo não está disponível em aplicativos em que as versões do .NET Framework são anteriores à versão 4.

Confira também