Matrices (F#)
Las matrices son colecciones mutables de tamaño fijo y de base cero de elementos de datos consecutivos del mismo tipo.
Crear matrices
Las matrices se pueden crear de varias maneras. Se puede crear una matriz pequeña colocando entre [| y |] valores consecutivos separados por punto y coma, tal y como se muestra en los siguientes ejemplos.
let array1 = [| 1; 2; 3 |]
También se puede poner cada elemento en una línea independiente, en cuyo caso el uso del punto y coma como separador es opcional.
let array1 =
[|
1
2
3
|]
El tipo de los elementos de la matriz se deduce a partir de los literales utilizados y debe ser el mismo para todos los elementos. En el código siguiente, se produce un error porque 1.0 es de tipo flotante, mientras que 2 y 3 son enteros.
// Causes an error.
// let array2 = [| 1.0; 2; 3 |]
También se pueden usar expresiones de secuencia para crear matrices. En el siguiente ejemplo, se crea una matriz de cuadrados de los enteros del 1 al 10.
let array3 = [| for i in 1 .. 10 -> i * i |]
Para crear una matriz donde todos los elementos se inicializan en cero, se utiliza Array.zeroCreate.
let arrayOfTenZeroes : int array = Array.zeroCreate 10
Tener acceso a elementos
Para obtener acceso a los elementos de una matriz, puede usar un operador punto (.) y corchetes ([y]).
array1.[0]
Los índices de matriz se inician en el 0.
También se puede obtener acceso a los elementos de una matriz mediante la notación de segmentos, que permite especificar un subrango de la matriz. A continuación se muestran ejemplos de la notación de segmentos.
// 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..]
Cuando se utiliza la notación de segmentos, se crea una nueva copia de la matriz.
Tipos de matrices y módulos
El tipo de todas las matrices de F# es el tipo Array de .NET Framework. Por consiguiente, las matrices de F# admiten toda la funcionalidad que está disponible en Array.
El módulo de biblioteca Microsoft.FSharp.Collections.Array admite las operaciones con matrices unidimensionales. Los módulos Array2D, Array3D y Array4D contienen funciones que admiten las operaciones con matrices de dos, tres y cuatro dimensiones, respectivamente. Para crear matrices con un rango superior a 4 se usa Array.
Funciones simples
Array.get obtiene un elemento. Array.length proporciona la longitud de una matriz. Array.set establece un elemento en el valor especificado. En el ejemplo de código siguiente, se muestra el uso de estas funciones.
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)
La salida es la siguiente.
0 1 2 3 4 5 6 7 8 9
Funciones que crean matrices
Hay varias funciones que crean matrices sin requerir una matriz existente. Array.empty crea una nueva matriz que no contiene ningún elemento. Array.create crea una matriz del tamaño especificado y establece todos los elementos en los valores proporcionados. Array.init crea una matriz a partir de la dimensión y la función especificadas para generar los elementos. Array.zeroCreate crea una matriz donde todos los elementos se inicializan en el valor cero del tipo de la matriz. En el siguiente código, se muestran estas funciones.
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
La salida es la siguiente.
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 crea una nueva matriz que contiene elementos que se han copiado de una matriz existente. Tenga en cuenta que se trata de una copia superficial, lo que significa que si el tipo de elemento es un tipo de referencia, solo se copia la referencia y no el objeto subyacente. En el siguiente ejemplo código se muestra cómo hacerlo.
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
El resultado del código anterior es el siguiente:
[|Test1; Test2; |]
[|; Test2; |]
La cadena Test1 solo aparece en la primera matriz porque, al crear un nuevo elemento, se sobrescribe la referencia en firstArray pero no se ve afectada la referencia original a una cadena vacía que aún se encuentra en secondArray. La cadena Test2 aparece en ambas matrices porque la operación Insert en el tipo StringBuilder afecta al objeto StringBuilder subyacente, al que se hace referencia en ambas matrices.
Array.sub genera una nueva matriz a partir de un subrango de una matriz. Para especificar el subrango, hay que proporcionar el índice de inicio y la longitud. En el código siguiente, se muestra el uso de Array.sub.
let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2
En la salida se muestra que la submatriz comienza en el elemento 5 y contiene 10 elementos.
[|5; 6; 7; 8; 9; 10; 11; 12; 13; 14|]
Array.append crea una nueva matriz combinando dos matrices existentes.
En el código siguiente, se muestra Array.append.
printfn "%A" (Array.append [| 1; 2; 3|] [| 4; 5; 6|])
La salida del código anterior es la siguiente.
[|1; 2; 3; 4; 5; 6|]
Array.choose selecciona elementos de una matriz para incluirlos en una nueva matriz. En el código siguiente, se muestra Array.choose. Observe que el tipo de elemento de la matriz no tiene que coincidir con el tipo del valor devuelto en el tipo de opción. En este ejemplo, el tipo de elemento es int y la opción es el resultado de una función polinómica, elem*elem - 1, representado como un número de punto flotante.
printfn "%A" (Array.choose (fun elem -> if elem % 2 = 0 then
Some(float (elem*elem - 1))
else
None) [| 1 .. 10 |])
La salida del código anterior es la siguiente.
[|3.0; 15.0; 35.0; 63.0; 99.0|]
Array.collect ejecuta la función especificada con cada uno de los elementos de una matriz y, a continuación, recopila los elementos generados por la función y los combina en una nueva matriz. Con el código siguiente, se muestra Array.collect.
printfn "%A" (Array.collect (fun elem -> [| 0 .. elem |]) [| 1; 5; 10|])
La salida del código anterior es la siguiente.
[|0; 1; 0; 1; 2; 3; 4; 5; 0; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|]
Array.concat toma una secuencia de matrices y las combina en una sola matriz. Con el código siguiente, se muestra Array.concat.
let multiplicationTable max = seq { for i in 1 .. max -> [| for j in 1 .. max -> (i, j, i*j) |] }
printfn "%A" (Array.concat (multiplicationTable 3))
La salida del código anterior es la siguiente.
[|(1, 1, 1); (1, 2, 2); (1, 3, 3); (2, 1, 2); (2, 2, 4); (2, 3, 6); (3, 1, 3);
(3, 2, 6); (3, 3, 9)|]
Array.filter toma una función de condición booleana y genera una nueva matriz que contiene únicamente los elementos de la matriz de entrada para los cuales se cumple la condición. En el código siguiente, se muestra Array.filter.
printfn "%A" (Array.filter (fun elem -> elem % 2 = 0) [| 1 .. 10|])
La salida del código anterior es la siguiente.
[|2; 4; 6; 8; 10|]
Array.rev genera una nueva matriz invirtiendo el orden de una matriz existente. En el código siguiente, se muestra Array.rev.
let stringReverse (s: string) =
System.String(Array.rev (s.ToCharArray()))
printfn "%A" (stringReverse("!dlrow olleH"))
La salida del código anterior es la siguiente.
"Hello world!"
El operador de canalización (|>) permite combinar fácilmente funciones del módulo Array que transforman las matrices, tal y como se muestra en el siguiente ejemplo.
[| 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"
El resultado es:
[|100; 36; 16; 4|]
Matrices multidimensionales
Se puede crear una matriz multidimensional, pero no existe ninguna sintaxis para escribir un literal de matriz multidimensional. Se usa el operador array2D para crear una matriz a partir de una secuencia de secuencias de elementos de matriz. Las secuencias pueden ser literales de matriz o de lista. En el siguiente código de ejemplo, se crea una matriz bidimensional.
let my2DArray = array2D [ [ 1; 0]; [0; 1] ]
También se puede utilizar la función Array2D.init para inicializar matrices de dos dimensiones. Además, existen funciones similares para las matrices de tres y cuatro dimensiones. Estas funciones toman una función que se utiliza para crear los elementos. Para crear una matriz bidimensional que contiene elementos establecidos en un valor inicial en lugar de especificar una función, utilice la función Array2D.create, que también está disponible para matrices de hasta cuatro dimensiones. En el ejemplo de código siguiente, se muestra primero cómo crear una matriz de matrices que contiene los elementos deseados y, a continuación, se usa Array2D.init para generar la matriz bidimensional deseada.
let arrayOfArrays = [| [| 1.0; 0.0 |]; [|0.0; 1.0 |] |]
let twoDimensionalArray = Array2D.init 2 2 (fun i j -> arrayOfArrays.[i].[j])
La sintaxis de indización y fragmentación de matrices se admite para matrices con un rango inferior o igual a 4. Cuando se especifica un índice en varias dimensiones, se utilizan comas para separar los índices, tal y como se muestra en el ejemplo de código siguiente.
twoDimensionalArray.[0, 1] <- 1.0
El tipo de una matriz bidimensional se escribe con el formato <type>[,] (por ejemplo, int[,], double[,]), mientras que el de una tridimensional se escribe con el formato <type>[,,], y así sucesivamente para las matrices de más dimensiones.
De las funciones disponibles para las matrices unidimensionales, solo hay un subconjunto que también está disponible para las matrices multidimensionales. Para obtener más información, vea Collections.Array (Módulo de F#), Collections.Array2D (Módulo de F#), Collections.Array3D (Módulo de F#) y Collections.Array4D (Módulo de F#).
Fragmentación de matrices y matrices multidimensionales
En una matriz bidimensional (una matriz), puede extraer una matriz más pequeña especificando intervalos y utilizando un carácter comodín (*) para especificar filas o columnas completas.
// 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]
A partir de F# 3.1, puede descomponer una matriz multidimensional en submatrices de dimensiones iguales o menores. Por ejemplo, puede obtener un vector de una matriz especificando una sola fila o columna.
// Get row 3 from a matrix as a vector:
matrix.[3, *]
// Get column 3 from a matrix as a vector:
matrix.[*, 3]
Puede usar esta sintaxis de fragmentación para tipos que implementan los operadores de acceso a elementos y los métodos sobrecargados GetSlice. Por ejemplo, el código siguiente crea un tipo de matriz que contiene la matriz 2D de F#, implementa una propiedad Item para proporcionar compatibilidad para la indización de matrices e implementa tres versiones de GetSlice. Si puede utilizar este código como una plantilla para los tipos de matriz, puede utilizar todas las operaciones de fragmentación que se describen en esta sección.
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 "%A" submatrix
let firstRow = test1.[0,*]
let secondRow = test1.[1,*]
let firstCol = test1.[*,0]
printfn "%A" firstCol
Funciones booleanas para matrices
Las funciones Array.exists y Array.exists2 comprueban los elementos de una o dos matrices, respectivamente. Estas funciones toman una función de prueba y devuelven true si hay un elemento (o par de elementos en el caso de Array.exists2) que cumple la condición.
En el código siguiente, se muestra el uso de Array.exists y Array.exists2. En estos ejemplos, se crean nuevas funciones aplicando solo uno de los argumentos; en estos casos, se trata del argumento de función.
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|])
La salida del código anterior es la siguiente.
true
false
false
true
De forma similar, la función Array.forall comprueba una matriz para determinar si todos los elementos cumplen una condición booleana. La variante Array.forall2 hace lo mismo aplicando una función booleana a los elementos de dos matrices de igual longitud. En el código siguiente, se muestra el uso de estas funciones.
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 |])
La salida de estos ejemplos es la siguiente.
false
true
true
false
Buscar en las matrices
Array.find toma una función booleana y devuelve el primer elemento para el cual la función devuelve true o produce una excepción KeyNotFoundException si no se encuentra ningún elemento que cumpla la condición. Array.findIndex es similar a Array.find, excepto en que devuelve el índice del elemento en lugar del propio elemento.
En el siguiente código, se utilizan Array.find y Array.findIndex para buscar un número que sea un cuadrado perfecto y un cubo perfecto.
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
La salida es la siguiente.
The first element that is both a square and a cube is 64 and its index is 62.
Array.tryFind es similar a Array.find, excepto en que su resultado es un tipo de opción, y devuelve None si no se encuentra ningún elemento. Se debe usar Array.tryFind en lugar de Array.find cuando no se sabe si hay un elemento coincidente en la matriz. De forma similar, Array.tryFindIndex es parecido a Array.findIndex, excepto en que el tipo de opción es el valor devuelto. Si no se encuentra ningún elemento, la opción es None.
En el código siguiente, se muestra el uso de Array.tryFind. Este código depende del 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 |]
La salida es la siguiente.
Found an element: 1
Found an element: 729
Se debe usar Array.tryPick cuando se necesita transformar un elemento además de buscarlo. El resultado es el primer elemento para el que la función devuelve el elemento transformado como un valor de opción, o bien None si dicho elemento no se encuentra.
En el código siguiente, se muestra el uso de Array.tryPick. En este caso, en lugar de una expresión lambda, se definen varias funciones auxiliares locales para simplificar el 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 |]
La salida es la siguiente.
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.
Realizar cálculos con matrices
La función Array.average devuelve el promedio de todos los elementos de una matriz. Se limita a los tipos de elemento que admiten la división exacta por un entero, como los tipos de punto flotante. Sin embargo, quedan excluidos los tipos enteros. La función Array.averageBy devuelve el promedio de los resultados de llamar a una función en cada elemento. Para una matriz de tipo entero, se puede utilizar Array.averageBy y hacer que la función convierta cada elemento en un tipo de punto flotante a fin de efectuar el cálculo.
Se utiliza Array.max o Array.min para obtener el elemento máximo o mínimo si el tipo de elemento lo admite. De forma similar, Array.maxBy y Array.minBy permiten ejecutar primero una función, por ejemplo, para realizar la transformación en un tipo que admita la comparación.
Array.sum suma los elementos de una matriz; Array.sumBy llama a una función en cada elemento y suma los resultados.
Para ejecutar una función en todos los elementos de una matriz sin almacenar los valores devueltos, se utiliza Array.iter. Para una función que afecte a dos matrices de igual longitud, se utiliza Array.iter2. Si también es preciso mantener una matriz de los resultados de la función, se utiliza Array.map o Array.map2, que actúa sobre dos matrices a la vez.
Al igual que Array.mapi y Array.mapi2, las variantes Array.iteri y Array.iteri2 permiten utilizar el índice del elemento en el cálculo.
Las funciones Array.fold, Array.foldBack, Array.reduce, Array.reduceBack, Array.scan y Array.scanBack ejecutan algoritmos en los que se usan todos los elementos de una matriz. De forma similar, las variantes Array.fold2 y Array.foldBack2 actúan sobre dos matrices.
Estas funciones para realizar cálculos se corresponden con las funciones del mismo nombre en el módulo List. Para obtener ejemplos de su uso, vea Listas (F#).
Modificar matrices
Array.set establece un elemento en el valor especificado. Array.fill establece un intervalo de elementos de una matriz en el valor especificado. En el código siguiente, se muestra un ejemplo de Array.fill.
let arrayFill1 = [| 1 .. 25 |]
Array.fill arrayFill1 2 20 0
printfn "%A" arrayFill1
La salida es la siguiente.
[|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|]
Se puede utilizar Array.blit para copiar una subsección de una matriz en otra matriz.
Convertir de unos tipos a otros
Array.ofList crea una matriz a partir de una lista. Array.ofSeq crea una matriz a partir de una secuencia. Array.toList y Array.toSeq convierten el tipo de la matriz en estos otros tipos de colección.
Ordenar matrices
Se utiliza Array.sort para ordenar una matriz mediante la función de comparación genérica. Se utiliza Array.sortBy para especificar una función que genera un valor, denominado clave, por el que se va a ordenar mediante la función de comparación genérica. Se utiliza Array.sortWith cuando se desea proporcionar una función de comparación personalizada. Array.sort, Array.sortBy y Array.sortWith devuelven la matriz ordenada como una nueva matriz. Las variantes Array.sortInPlace, Array.sortInPlaceBy y Array.sortInPlaceWith modifican la matriz existente en lugar de devolver una nueva.
Matrices y tuplas
Las funciones Array.zip y Array.unzip convierten matrices de tuplas de dos elementos en tuplas de matrices y viceversa. Las funciones Array.zip3 y Array.unzip3 son similares, excepto en que se usan con tuplas de tres elementos o tuplas de tres matrices.
Cálculos en paralelo con matrices
El módulo Array.Parallel contiene funciones para realizar cálculos en paralelo con matrices. Este módulo no está disponible en las aplicaciones destinadas a versiones de .NET Framework anteriores a la versión 4.