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