Matrices (F#)
Las matrices son colecciones mutables, de tamaño fijo y base cero, de elementos de datos consecutivos que son todos del mismo tipo.
Creación de matrices
Puede crear matrices de varias maneras. Puede crear una matriz pequeña enumerando valores consecutivos entre [|
y |]
separados por punto y coma, como se muestra en los ejemplos siguientes.
let array1 = [| 1; 2; 3 |]
También puede colocar cada elemento en una línea independiente, en cuyo caso el separador de punto y coma es opcional.
let array1 =
[|
1
2
3
|]
El tipo de los elementos de matriz se deduce de los literales usados y debe ser coherente.
// 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 |]
El código siguiente produce un error porque 3.0 es un valor float y 1 y 2 son enteros.
// Causes an error. The 3.0 (float) cannot be converted to integer implicitly.
// let array3 = [| 1; 2; 3.0 |]
El código siguiente también produce un error porque 1,2
es una tupla y 3
es un entero.
// Causes an error too. The 3 (integer) cannot be converted to tuple implicitly.
// let array4 = [| 1, 2; 3 |]
También puede usar expresiones de secuencia para crear matrices. A continuación se muestra un ejemplo que crea una matriz de cuadrados de enteros de 1 a 10.
let array3 = [| for i in 1 .. 10 -> i * i |]
Para crear una matriz en la que todos los elementos se inicialicen en cero, use Array.zeroCreate
.
let arrayOfTenZeroes : int array = Array.zeroCreate 10
Acceso a elementos
Puede acceder a los elementos de matriz mediante corchetes ([
y ]
). La sintaxis de punto original (.[index]
) todavía se admite, pero ya no se recomienda a partir de la versión 6.0 de F#.
array1[0]
Los índices de matriz comienzan en 0.
También puede acceder a los elementos de la matriz mediante la notación de sectores, lo que le permite especificar un subrango de la matriz. A continuación se muestran ejemplos de notación de sectores.
// 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 usa la notación de sectores, se crea una nueva copia de la matriz.
Tipos y módulos de matriz
El tipo de todas las matrices de F# es el tipo de .NET Framework System.Array. Por lo tanto, las matrices en F# admiten toda la funcionalidad disponible de System.Array.
El módulo Array
admite operaciones en matrices unidimensionales. Los módulos Array2D
, Array3D
y Array4D
contienen funciones que admiten operaciones en matrices de dos, tres y cuatro dimensiones, respectivamente. Puede crear matrices de un rango mayor que cuatro mediante System.Array.
Funciones sencillas
Array.get
obtiene un elemento. Array.length
indica la longitud de una matriz. Array.set
establece un elemento en un valor especificado. En el siguiente ejemplo de código 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
Varias funciones crean matrices sin necesidad de una matriz existente. Array.empty
crea una nueva matriz que no contiene ningún elemento. Array.create
crea una matriz de un tamaño especificado y establece todos los elementos en los valores proporcionados. Array.init
crea una matriz a partir de una dimensión y una función determinadas para generar los elementos. Array.zeroCreate
crea una matriz en la que todos los elementos se inicializan en el valor cero para el tipo de la matriz. En el código siguiente 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 copian de una matriz existente. Tenga en cuenta que la copia es una copia superficial, lo que significa que si el tipo de elemento es un tipo de referencia, solo se copia la referencia, 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
La salida del código anterior es la siguiente:
[|Test1; Test2; |]
[|; Test2; |]
La cadena Test1
solo aparece en la primera matriz porque la operación de creación de un nuevo elemento sobrescribe la referencia de firstArray
, pero no afecta a la referencia original a una cadena vacía que todavía está presente en secondArray
. La cadena Test2
aparece en ambas matrices porque la operación Insert
en el tipo System.Text.StringBuilder afecta al objeto subyacente System.Text.StringBuilder, 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, proporcione el índice y la longitud iniciales. En el código siguiente se muestra cómo usar Array.sub
.
let a1 = [| 0 .. 99 |]
let a2 = Array.sub a1 5 10
printfn "%A" a2
La salida 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
combina dos matrices existentes para crear una nueva.
En el código siguiente se muestra Ar.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 los elementos de una matriz que se van a incluir en una nueva matriz. En el código siguiente se muestra Array.choose
. Tenga en cuenta 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
, 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 una función especificada en cada elemento de matriz de una matriz existente y, a continuación, recopila los elementos generados por la función y los combina en una nueva matriz. En 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. En el código siguiente se muestra 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
toma una función de condición booleana y genera una nueva matriz que contiene solo los elementos de la matriz de entrada para la que la condición es verdadera. 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 al revertir 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!"
Puede combinar fácilmente funciones en el módulo de matriz que transformen matrices mediante el operador de canalización (|>
), como se muestra en el ejemplo siguiente.
[| 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 hay sintaxis para escribir un literal de matriz multidimensional. Use 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 lista. Por ejemplo, el código siguiente crea una matriz bidimensional.
let my2DArray = array2D [ [ 1; 0]; [0; 1] ]
También puede usar la función Array2D.init
para inicializar matrices de dos dimensiones y hay funciones similares disponibles para matrices de tres y cuatro dimensiones. Estas funciones toman una función que se usa para crear los elementos. Para crear una matriz bidimensional que contenga elementos establecidos en un valor inicial en lugar de especificar una función, use 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 contienen 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 indexación y segmentación de matrices se admite para matrices de hasta el rango 4. Al especificar un índice en varias dimensiones, se usan comas para separar los índices, como se muestra en el ejemplo de código siguiente.
twoDimensionalArray[0, 1] <- 1.0
El tipo de una matriz bidimensional se escribe como <type>[,]
(por ejemplo, int[,]
, double[,]
), y el tipo de una matriz tridimensional se escribe como <type>[,,]
, y así sucesivamente para matrices de más dimensiones.
Solo un subconjunto de las funciones disponibles para matrices unidimensionales también está disponible para matrices multidimensionales.
Segmentación de matrices y matrices multidimensionales
En una matriz bidimensional, puede extraer una submatriz especificando rangos y usando 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]
Puede descomponer una matriz multidimensional en submatrices de la misma dimensión o inferior. 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 segmentació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 Matrix que encapsula la matriz F# 2D, implementa una propiedad Item para proporcionar compatibilidad con la indexación de matrices e implementa tres versiones de GetSlice
. Si puede usar este código como plantilla para los tipos de matriz, puede usar todas las operaciones de segmentació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 $"{submatrix}"
let firstRow = test1[0,*]
let secondRow = test1[1,*]
let firstCol = test1[*,0]
printfn $"{firstCol}"
Funciones booleanas en matrices
Las funciones Array.exists
y Array.exists2
prueban los elementos en una o dos matrices, respectivamente. Estas funciones toman una función de prueba y devuelven true
si hay un elemento (o par de elementos para Array.exists2
) que satisface la condición.
En el código siguiente se muestra cómo usar Array.exists
y Array.exists2
. En estos ejemplos, las nuevas funciones se crean aplicando solo uno de los argumentos; en estos casos, el 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
prueba una matriz para determinar si cada elemento cumple una condición booleana. La variación Array.forall2
hace lo mismo mediante una función booleana que implique 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
Búsqueda de matrices
Array.find
toma una función booleana y devuelve el primer elemento para el que la función devuelve true
o genera un System.Collections.Generic.KeyNotFoundException si no se encuentra ningún elemento que cumpla la condición. Array.findIndex
es como Array.find
, salvo que devuelve el índice del elemento en lugar del propio elemento.
El código siguiente usa Array.find
y Array.findIndex
para localizar 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 como Array.find
, salvo que su resultado es un tipo de opción y devuelve None
si no se encuentra ningún elemento. Array.tryFind
se debe usar en lugar de Array.find
cuando no se sabe si un elemento coincidente está en la matriz. Del mismo modo, Array.tryFindIndex
es similar a Array.findIndex
, salvo 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 cómo usar 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
Failed to find a matching element.
Use Array.tryPick
cuando necesite transformar un elemento además de encontrarlo. El resultado es el primer elemento para el que la función devuelve el elemento transformado como un valor de opción, o None
si no se encuentra dicho elemento.
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.
Did not find an element that is both a perfect square and a perfect cube.
Realización de cálculos en matrices
La función Array.average
devuelve el promedio de cada elemento de una matriz. Se limita a los tipos de elemento que admiten la división exacta por un entero, lo que incluye tipos de punto flotante, pero no 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, puede usar Array.averageBy
y hacer que la función convierta cada elemento en un tipo de número de punto flotante para el cálculo.
Use Array.max
o Array.min
para obtener el elemento máximo o mínimo, si el tipo de elemento lo admite. Del mismo modo, Array.maxBy
y Array.minBy
permiten ejecutar primero una función, quizás para transformarla en un tipo que admita la comparación.
Array.sum
agrega los elementos de una matriz y Array.sumBy
llama a una función en cada elemento y agrega los resultados juntos.
Para ejecutar una función en cada elemento de una matriz sin almacenar los valores devueltos, use Array.iter
. Para una función que implique dos matrices de igual longitud, use Array.iter2
. Si también necesita mantener una matriz de los resultados de la función, use Array.map
o Array.map2
, que funciona en dos matrices a la vez.
Las variaciones Array.iteri
y Array.iteri2
permiten que el índice del elemento participe en el cálculo; lo mismo sucede con Array.mapi
y Array.mapi2
.
Las funciones Array.fold
, Array.foldBack
, Array.reduce
, Array.reduceBack
, Array.scan
y Array.scanBack
ejecutan algoritmos que implican todos los elementos de una matriz. Del mismo modo, las variaciones Array.fold2
y Array.foldBack2
realizan cálculos en dos matrices.
Estas funciones para realizar cálculos corresponden a las funciones del mismo nombre en el módulo List. Para obtener ejemplos de uso, vea Listas.
Modificación de matrices
Array.set
establece un elemento en un valor especificado. Array.fill
establece un rango de elementos de una matriz en un valor especificado. En el siguiente código se proporciona 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|]
Puede usar Array.blit
para copiar una subsección de una matriz en otra matriz.
Conversión a otros tipos y viceversa
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 a estos otros tipos de colección del tipo de matriz.
Ordenación de matrices
Use Array.sort
para ordenar una matriz mediante la función de comparación genérica. Use Array.sortBy
para especificar una función que genere un valor, denominado clave, para ordenar mediante la función de comparación genérica en la clave. Use Array.sortWith
si 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 variaciones 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 pares de tuplas en tuplas de matrices y viceversa. Array.zip3
y Array.unzip3
son similares, pero funcionan con tuplas de tres elementos o tuplas de tres matrices.
Cálculos paralelos en matrices
El módulo Array.Parallel
contiene funciones para realizar cálculos paralelos en matrices. Este módulo no está disponible en aplicaciones destinadas a versiones de .NET Framework anteriores a la versión 4.