introduction
La base du travail avec les matrices (dans cet article, nous ne considérerons que les matrices bidimensionnelles) est une puissante théorie mathématique du domaine de l'algèbre linéaire. Une définition ou une action en découle, une fonction en appelle une autre. Par conséquent, pour l'implémentation fonctionnelle de la fonctionnelle des opérations mathématiques sur les matrices, les langages fonctionnels s'adaptent très bien. Dans cet article, nous allons examiner des exemples spécifiques en F # et donner des commentaires détaillés sur la façon dont cela fonctionne. Étant donné que F # fait partie de la famille .NET, la fonctionnalité résultante peut être utilisée sans aucun problème dans d'autres langages impératifs, par exemple C #.Définition et implémentation d'une matrice en F #
Les matrices sont la partie fondamentale et la plus importante de l'algèbre linéaire. Les matrices sont souvent utilisées dans la programmation, par exemple, dans la modélisation 3D ou le développement de jeux. Bien sûr, le vélo a longtemps été inventé et les cadres nécessaires pour travailler avec des matrices sont déjà prêts, et ils peuvent et doivent être utilisés. Cet article ne vise pas à inventer un nouveau framework, mais montre l'implémentation d'opérations mathématiques de base pour travailler avec des matrices dans un style fonctionnel en utilisant le langage de programmation F #. En examinant le matériau, nous nous tournerons vers la théorie mathématique des matrices et verrons comment il peut être implémenté dans le code.Rappelons d'abord ce qu'est une matrice? La théorie nous dit ce qui suitUn tableau rectangulaire de nombres contenant m lignes et n colonnes est appelé matrice de taille mxn
Les matrices sont généralement désignées par des majuscules de l'alphabet latin et s'écrivent comme suit:Ou peuA=(aij)
Pour travailler avec des matrices en F #, nous allons créer un enregistrement basé sur un tableau à deux dimensions, que nous développerons avec des méthodes utiles afin d'effectuer les opérations mathématiques nécessaires sur celui-ci.type Matrix = { values: int[,] }
with
//
end
Ajouter une méthode d'assistance pour initialiser l'enregistrement avec un tableau à deux dimensionsstatic member ofArray2D (values: int [,]) =
{ values = values }
L'argument d'entrée de la fonction sera un tableau à deux dimensions et, à sa sortie, un enregistrement de type Matrix . Voici un exemple d'initialisation d'enregistrement.let a = array2D [[1;0;2]
[3;1;0]]
let A = Matrix.ofArray2D a
Deux matrices A = (aij) et B = (bij) sont appelées égales si elles coïncident par élément, c'est-à-dire aij = bij pour tout i = 1,2 ..., m et j = 1,2 ... n
Pour implémenter cette règle, nous utiliserons l'opérateur substitué == et ajouterons quelques fonctions utiles, dont nous aurons également besoin à l'avenir.static member sizes matrix =
let rows = matrix.values.[*,0].Length
let cols = matrix.values.[0,*].Length
(rows, cols)
static member isEquallySized matrix1 matrix2 =
let dim1 = Matrix.sizes matrix1
let dim2 = Matrix.sizes matrix2
(dim1 = dim2)
static member (==) (matrix1, matrix2) =
if not (Matrix.isEquallySized matrix1 matrix2) then false
else
not (matrix1.values
|> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true)
|> Seq.cast<bool>
|> Seq.contains false)
Examinons de plus près le code ci-dessus. Comme vous pouvez le voir, il existe trois fonctions. La première fonction de tailles renvoie la dimension de la matrice sous forme de tuple. Puisque nous travaillons uniquement avec des matrices rectangulaires, pour obtenir le nombre de lignes, nous prenons une tranche complète de la première colonne et retournons sa longueur.let rows = matrix.values.[*,0].Length
La détermination du nombre de colonnes fonctionne de la même manière - nous obtenons une tranche complète de la première ligne et retournons sa longueur.La fonction isEquallySized suivante compare la dimension de deux matrices et renvoie vrai si elles sont égales. Pour ce faire, il utilise la fonction de tailles prêtes à l'emploi et compare simplement les résultats.L'opérateur == pour comparer élément par élément deux matrices semble plus compliqué, mais maintenant vous verrez qu'il est également simple.Avant de comparer deux matrices, nous comparons leur dimension. S'ils ne sont pas égaux, il n'y a pas d'autre point à vérifier, car il est déjà clair que les matrices ne seront pas égales.if not (Matrix.isEquallySized matrix1 matrix2) then false
Ensuite, sur la base des matrices originales matrice1 et matrice2, nous formons une nouvelle matrice remplie de vrai ou de faux , selon que les cellules correspondantes des deux matrices coïncident.matrix1.values
|> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true
La fonction Array2D.mapi itère sur tous les éléments de matrix1 et transmet trois paramètresx au gestionnaire - index de ligney - index de colonnev -contenu de la cellule Nous comparons le contenu de la cellule v avec la cellule de matrice correspondante2 et s'ils sont égaux, alors écrire vrai , sinon faux .S'il y a au moins une cellule avec le faux élément , cela signifie que les matrices ne sont pas égales les unes aux autres.Comme Array2D ne contient pas de méthodes de filtrage ou de recherche, nous allons l'implémenter nous-mêmes. Pour ce faire, nous décomposons la matrice en une liste linéaire|> Seq.cast<bool>
Et trouver au moins un décalage|> Seq.contains false
La fonction Seq.contains renverra true si au moins une fausse valeur est trouvée dans la liste développée . Par conséquent, nous devons inverser le résultat afin que l'opérateur == fonctionne comme nous le voulonselse
not (matrix1.values
|> Array2D.mapi (fun x y v -> if matrix2.values.[x, y] <> v then false else true)
|> Seq.cast<bool>
|> Seq.contains false)
Une matrice O est appelée matrice nulle ou nulle si tous ses éléments sont égaux à zéro.
static member O rows cols =
let array2d = Array2D.zeroCreate rows cols
{ values = array2d }
Un exemple d'utilisation de cette fonctionlet AO = Matrix.O 5 5
Je crois qu'il n'y a rien de compliqué qui nécessite une explication, alors nous continuons.Une matrice dont le nombre de lignes est égal au nombre de colonnes et égal à n est appelée matrice carrée d'ordre n
Ainsi, la matrice carrée a la forme.A=[a11a12...a1na21a22...a2n............an1an2...ann]
Dans le cadre de cette règle, nous allons créer une fonction qui transforme une matrice rectangulaire en matrice carrée en coupant tous les éléments qui ne tombent pas dans le carré.static member toSquare matrix =
//
let dim = Matrix.sizes matrix
//
let colCount: int = snd dim
//
let rowCount: int = fst dim
//
let length = System.Math.Min (colCount, rowCount)
//
//
let zero = Array2D.zeroCreate length length
//
let square = zero |> Array2D.mapi (fun x y _ -> matrix.values.[x, y])
//
{ values = square }
Les commentaires dans le code source expliquent comment fonctionne la fonction, alors continuons.Une matrice carrée est appelée triangulaire si tous ses éléments en dessous de la diagonale principale sont égaux à zéro, c'est-à-dire la matrice triangulaire a la forme
T=(a11a12...a1n0a22...a2n............00...ann)
Ci-dessous se trouve le code de fonction qui convertit la matrice d'origine en triangulaire. Mais dans notre fonction, nous travaillerons avec une matrice rectangulaire, c'est-à-dire qu'elle peut ne pas être carrée. Le lecteur peut facilement modifier le code de fonction afin qu'il renvoie une matrice triangulaire carrée en utilisant la fonction que nous avons examinée précédemment.static member T matrix =
let values = matrix.values |> Array2D.mapi (fun x y v -> if y < x then 0 else v)
{ values = values }
La fonction Array2D.mapi convertit le tableau bidimensionnel d'origine en un nouveau à l'aide d'un gestionnaire qui prend trois paramètresx - numéro de ligney - numéro de colonnev - contenu de la celluleif y < x then 0 else v
Ici, nous vérifions si l'élément est en dessous de la diagonale principale et si oui, remplissons la cellule 0. Sinon, la valeur initiale de la matrice d'entrée.Voici un exemple d'utilisation de cette fonction.let a = array2D [[1;2;3]
[4;5;6]
[7;8;9]
[10;11;12]]
let A = Matrix.ofArray2D a
let R = Matrix.triangular A
printfn "origin = \n %A" A.values
printfn "triangular = \n %A" R.values
Nous obtenons le résultat suivantorigin =
[[1; 2; 3]
[4; 5; 6]
[7; 8; 9]
[10; 11; 12]]
triangular =
[[1; 2; 3]
[0; 5; 6]
[0; 0; 9]
[0; 0; 0]]
Une matrice carrée est appelée diagonale si tous ses éléments situés à l'extérieur de la diagonale principale sont égaux à zéro
static member D matrix =
let diagonal = matrix.values |> Array2D.mapi (fun x y v -> if x <> y then 0 else v)
{ values = diagonal }
Cette fonction est très similaire à la précédente, seule la condition de vérification est différente. Voici un exemple d'utilisationlet a = array2D [[1;2;3]
[4;5;6]
[7;8;9]
[10;11;12]]
let A = Matrix.ofArray2D a
let R = Matrix.D A
printfn "origin = \n %A" A.values
printfn "diagonal = \n %A" R.values
origin =
[[1; 2; 3]
[4; 5; 6]
[7; 8; 9]
[10; 11; 12]]
diagonal =
[[1; 0; 0]
[0; 5; 0]
[0; 0; 9]
[0; 0; 0]]
La matrice diagonale est unitaire et est notée E si tous ses éléments situés sur la diagonale principale sont égaux à l'unité
E=(10...001...0............00...1)
L'implémentation d'une telle matrice sur F # ressemble à cecistatic member E rows cols =
let array2d = Array2D.init rows cols (fun x y -> if x = y then 1 else 0)
{ values = array2d }
Opérations matricielles avec F #
Un certain nombre d'actions peuvent être effectuées sur des matrices, ainsi que sur des nombres, dont certaines sont similaires aux opérations sur les nombres, et certaines sont spécifiques.La somme de deux matrices Amn = (aij) et Bmn = (bij) de même taille est la matrice de même taille A + B = Cmn = (cij) , dont les éléments sont égaux à la somme des éléments des matrices A et B situées aux endroits correspondants
cij=aij+bij,i=1,2,...,m,j=1,2,...,n
Exemple, pour des matrices données A et B, on trouve la somme A + BA=(231−506),B=(−331720),A+B=(−162226)
Considérez le code pour ajouter deux matricesstatic member (+) (matrix1, matrix2) =
if Matrix.isEquallySized matrix1 matrix2 then
let array2d = matrix1.values |> Array2D.mapi (fun x y v -> matrix2.values.[x, y] + v)
{ values = array2d }
else failwith "matrix1 is not equal to matrix2"
Avant d'ajouter des matrices, vous devez vous assurer que leur dimension coïncide, sinon la fonction lève une exception. Voici un exemple d'utilisation de cette fonctionlet a = array2D [[2;3]
[1;-5]
[0;6]]
let A = Matrix.ofArray2D a
let b = array2D [[-3;3]
[1;7]
[2;0]]
let B = Matrix.ofArray2D b
let R = A+B
printfn "A+B =\n %A" R.values
A+B =
[[-1; 6]
[2; 2]
[2; 6]]
Le produit de la matrice A = (aij) par le nombre k est la matrice kA = (kaij) de même taille que la matrice A obtenue en multipliant tous les éléments de la matrice A par le nombre k
Exemple, pour une matrice A donnée on trouve la matrice 3AA=(230154),3A=(69031512)
static member (*) (value, matrix) =
let array2d = matrix.values |> Array2D.mapi (fun _ _ v -> v * value)
{ values = array2d }
Matrice -A = (- 1) * A sera appelée la matrice opposée A . A partir de cette définition, nous procédons en douceur à la suivanteLa différence des matrices A et B de tailles égales est la somme de la matrice A et de la matrice opposée à B
static member (-) (matrix1: Matrix, matrix2: Matrix) =
if Matrix.isEquallySized matrix1 matrix2 then
matrix1 + (-1)*matrix2
else failwith "matrix1 is not equal to matrix2"
Deux matrices sont dites cohérentes si le nombre de colonnes de la première est égal au nombre de lignes de la seconde
A=(2103),B=(05211437)
static member isMatched matrix1 matrix2 =
let row1Count = matrix1.values.[0,*].Length
let col2Count = matrix2.values.[*,0].Length
row1Count = col2Count
La vérification de la cohérence de la matrice est nécessaire pour les multiplier.Le produit AB matrice cohérente Amn = (aij) et Bnp = (bjk) est la matrice Cmn = (cik) , l'élément cik est calculé comme la somme des éléments i- ème ligne de la matrice A et des éléments correspondants de la k ème colonne de la matrice B
Calculer le produit des matricesA=(102310),B=(−1051−20)
Décision de déterminer le produit des matricesAB=(1(−1)+0∗5+2(−2)1∗0+0∗1+2∗03(−1)+1∗5+0(−2)3∗0+1∗1+0∗0)=(−5021)
Considérez le code pour multiplier deux matricesstatic member (*) (matrix1, (matrix2: Matrix)) =
if Matrix.isMatched matrix1 matrix2 then
let row1Count = matrix1.values.[*,0].Length
let col2Count = matrix2.values.[0,*].Length
let values = Array2D.zeroCreate row1Count col2Count
for r in 0..row1Count-1 do
for c in 0..col2Count-1 do
let row = Array.toList matrix1.values.[r,*]
let col = Array.toList matrix2.values.[*,c]
let cell = List.fold2 (fun acc val1 val2 -> acc + (val1 * val2)) 0 row col
values.[r,c] <- cell
{ values = values }
else failwith "matrix1 is not matched to matrix2"
Examinons le code plus en détail.Avant la multiplication, vous devez vous assurer que les matrices sont cohérentesif Matrix.isMatched matrix1 matrix2 then
La matrice résultante aura une dimension dans laquelle le nombre de lignes est le même que la première matrice et le nombre de colonnes est le même que la deuxième matricelet row1Count = matrix1.values.[*,0].Length
let col2Count = matrix2.values.[0,*].Length
//
let values = Array2D.zeroCreate row1Count col2Count
Après cela, nous parcourons toutes les lignes et toutes les colonnes des matrices originalesfor r in 0..row1Count-1 do
for c in 0..col2Count-1 do
let row = Array.toList matrix1.values.[r,*]
let col = Array.toList matrix2.values.[*,c]
Nous calculons la valeur totale de chaque cellulelet cell = List.fold2 (fun acc val1 val2 -> acc + (val1 * val2)) 0 row col
La fonction List.fold2 reçoit deux listes en entrée (ligne et colonne) et transmet les paramètres suivants au gestionnaireacc - l'accumulateur contenant le résultat du calcul précédentval1 - le contenu de la cellule du premier tableau. Dans notre cas, il s'agit de la ligne de la première matriceval2 - le contenu de la cellule du deuxième tableau, c'est-à-dire les colonnes de la deuxième matrice.Puisque les matrices sont cohérentes, nous sommes sûrs de ne pas aller au-delà des tableaux.Le gestionnaire ajoute à l'accumulateur un produit de cellules provenant de lignes et d'une colonne, et la valeur résultante sera passée à l'itération suivante. Ainsi, le résultat final de la fonction List.fold2sera la valeur finale des produits de deux matrices. Il ne reste plus qu'à les remplir avec la matrice vide précédemment crééevalues.[r,c] <- cell
Qui reviendra en conséquence{ values = values }
Voici un exemple d'utilisation de cette fonctionlet a = array2D [[1;0;2]
[3;1;0]]
let A = Matrix.ofArray2D a
let b = array2D [[-1;0]
[5;1]
[-2;0]]
let B = Matrix.ofArray2D b
let R = A*B
printfn "A*B =\n %A" R.values
A1*B1 =
[[-5; 0]
[2; 1]]
Si k ∈ N, alors la kième puissance d'une matrice carrée A est le produit de k matrices A
Ak=A∗A∗...A(k−)
Considérez le code sur F # pour le produit d'une matrice à une puissance. La récursivité de la queue sera utilisée ici afin de ne pas faire déborder la pile avec de grandes puissances. La récursivité de queue est une récursivité que le compilateur convertit finalement en boucle. Si possible, il est recommandé que vous utilisiez toujours la récursivité de queue au lieu de la normale, mais pour cela, vous avez besoin de chaque trame de récursivité pour retourner la valeur calculée finale. Cette valeur est généralement appelée accumulateur et est transmise à la trame de récursivité suivante. Autrement dit, contrairement à la récursivité régulière, qui renvoie la valeur calculée vers le haut de la pile, la récursivité de queue transmet la valeur calculée vers le bas de la pile. Chaque nouvelle trame de récursivité effectue ses propres calculs et les ajoute à la valeur précédemment calculée, qui est stockée dans la batterie. Après ça,Comme la dernière image de la récursivité a fonctionné, l'accumulateur a déjà une valeur calculée, qui revient simplement en conséquence.Ainsi, le compilateur peut optimiser le code et le convertir en boucle régulière.
//
// matrix -
// value -
static member (^^) (matrix, value) =
// ,
// m -
// p =
let inRecPow m p =
//
// acc - . Matrix
// p -
//
let rec recPow acc p =
//
match p with
| x when x > 0 ->
//
// ,
let nextAcc = acc*m
//
recPow nextAcc (x-1)
// ,
| _ -> acc
// ,
let dim = Matrix.sizes matrix
let colCount = snd dim
let rowCount = fst dim
let u = Matrix.E rowCount colCount
//
recPow u p
// ,
let powMatrix = inRecPow matrix value
//
{ values = powMatrix.values }
Le code contient des commentaires détaillés sur son fonctionnement. Nécessite une petite explication, pourquoi la matrice unitaire est-elle utilisée? Il est nécessaire pour la première trame de récursivité et sert de valeur de base à l'accumulateur, dans lequel le résultat final sera accumulé.Voici un exemple d'utilisation de notre fonctionA=(102−1)
A2=AA=(102−1)(102−1)=(1001)
A3=AA2=(102−1)(1001)=(102−1)
Nous calculons le produit suivantf(A)=2A3−4A2+3E
Où E est la matrice d'identité. Puisque nous ne pouvons pas ajouter un nombre à la matrice, nous devons ajouter 3E .2(102−1)−4(1001)+3(1001)=(104−3)
//
static member (+) (matrix, (value: int)) =
let dim = Matrix.sizes matrix
let r = fst dim
let c = snd dim
//
let unit = Matrix.E r c
//
value*unit + matrix
let a = array2D [[1;0]
[2;-1]]
let A = Matrix.ofArray2D a
let R = 2*(A^^3) - 4*(A^^2) + 3
printfn "2*A^3 - 4*A^2 + 3 =\n %A" R.values
2*A5^3 - 4*A5^2 + 3 =
[[1; 0]
[4; -3]]
Une matrice A T dont les colonnes sont composées de rangées de matrice A avec les mêmes nombres et la même séquence d'éléments est appelée transposée en matrice A
static member transpose matrix =
let dim = Matrix.sizes matrix
let rows = fst dim
let cols = snd dim
//
let tMatrix = Matrix.O cols rows
//
matrix.values |> Array2D.iteri(fun x y v -> tMatrix.values.[y, x] <- v)
//
tMatrix
Exemple d'utilisationlet a = array2D [[1;2;3]
[4;5;6]
[7;8;9]
[10;11;12]]
let A = Matrix.ofArray2D a
let R6 = Matrix.T A
printfn "origin = \n %A" A.values
printfn "transpose = \n %A" R.values
origin =
[[1; 2; 3]
[4; 5; 6]
[7; 8; 9]
[10; 11; 12]]
transpose =
[[1; 4; 7; 10]
[2; 5; 8; 11]
[3; 6; 9; 12]]
Sommaire
Dans cet article, nous avons examiné des exemples de mise en œuvre et d'utilisation de matrices issues de la théorie de l'algèbre linéaire. Ainsi que les opérations mathématiques de base sur celles-ci, en utilisant une approche fonctionnelle en F #. J'espère que le lecteur pourra apprécier la flexibilité offerte par les langages fonctionnels.Le code source complet du module de matrice, dont des fragments ont été examinés dans l'article, vous pouvez le trouver sur le github.Github Matrix.fs