I've been learning C# during my free time in the past months; before that, I was mostly writing Java, so the transition hasn't been too hard, but I've never had my code reviewed or read by someone else.
- Is there anything that could be improved, or maybe simplified?
- Am I doing anything that would be considered bad practice or 'smelly' ? If so, what should I be doing instead?
- I very rarely write comments; should I use them more, and specifically here, are there any methods or sections that could use some comments?
The class DMatrix
(D for double, as I intend to make one that uses floats instead, and another one for complex numbers), as you can guess, represents a matrix, or an m by n array of numbers. It started earlier today as rewrite of another implementation I had made in Java months ago; once I had finished translated that old Java code, I tried to include all the commonly used operations I could think of. I also tried to make the class easier and more intuitive to use, while keeping the code simple and -hopefully- readable, and trying to make use of all the C#-specific features I've learned about.
I've also included the Misc.Utils
class, which mostly contains some static methods for generating random numbers in different ranges or types. I spend most of my coding time reinventing (or re-implementing) the wheel, or writing things from scratch, as I find it to be a good way to learn and understand most concepts, while also allowing me to implement and play with some interesting math or math-related topics.
DMatrix.cs
using System;
using Mathlib.Misc;
namespace Mathlib.Matrix {
public class DMatrix {
private double[,] data;
public int NbRows {
get;
private set;
}
public int NbCols {
get;
private set;
}
public double this[int i, int j] {
get {
return data[i, j];
}
private set {
data[i, j] = value;
}
}
public static int Precision {
get;
set;
}
static DMatrix() {
Precision = 2;
}
public DMatrix(int n) {
if (n < 1) {
throw new Exception($"Cannot create matrix," +
$" N should be greater or equal to 1.\n\tN = {n}");
}
NbRows = NbCols = n;
Fill(0);
}
public DMatrix(int m, int n) {
if (m < 1) {
throw new Exception($"Cannot create matrix," +
$" M should be greater or equal to 1.\n\tM = {m}");
}
if (n < 1) {
throw new Exception($"Cannot create matrix," +
$" N should be greater or equal to 1.\n\tN = {n}");
}
NbRows = m;
NbCols = n;
data = new double[m, n];
Fill(0);
}
public DMatrix(int m, int n, double x) {
if (m < 1) {
throw new Exception($"Cannot create matrix," +
$" M should be greater or equal to 1.\n\tM = {m}");
}
if (n < 1) {
throw new Exception($"Cannot create matrix," +
$" N should be greater or equal to 1.\n\tN = {n}");
}
NbRows = m;
NbCols = n;
data = new double[m, n];
Fill(x);
}
public DMatrix(double[,] data) {
NbRows = data.GetLength(0);
NbCols = data.GetLength(1);
this.data = new double[NbRows, NbCols];
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
this.data[i, j] = data[i, j];
}
}
}
public DMatrix(DMatrix A) : this(A.data) { }
public void Fill(double x) {
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
data[i, j] = x;
}
}
}
public void Rand() {
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
data[i, j] = Utils.Rand();
}
}
}
public void Rand(double max) {
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
data[i, j] = Utils.Rand(max);
}
}
}
public void Rand(double min, double max) {
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
data[i, j] = Utils.Rand(min, max);
}
}
}
public void SwapRows(int row1, int row2) {
for (int i = 0; i < NbCols; i++) {
double temp = data[row1, i];
data[row1, i] = data[row2, i];
data[row2, i] = temp;
}
}
public static DMatrix operator -(DMatrix A) {
DMatrix res = new DMatrix(A.NbRows, A.NbCols);
for (int i = 0; i < A.NbRows; i++) {
for (int j = 0; j < A.NbCols; j++) {
res.data[i, j] = -A.data[i, j];
}
}
return res;
}
public static DMatrix operator ~(DMatrix A) {
return A.Transpose();
}
public static DMatrix operator +(DMatrix A, DMatrix B) {
if (A.NbRows != B.NbRows || A.NbCols != B.NbCols) {
throw new Exception($"Cannot compute sum, matrix dimensions do not match:\n\t" +
$"A : {A.NbRows}x{A.NbCols}\n\tB : {B.NbRows}x{B.NbCols}");
}
DMatrix res = new DMatrix(A.NbRows, A.NbCols);
for (int i = 0; i < A.NbRows; i++) {
for (int j = 0; j < A.NbCols; j++) {
res.data[i, j] = A.data[i, j] + B.data[i, j];
}
}
return res;
}
public static DMatrix operator -(DMatrix A, DMatrix B) {
return A + (-B);
}
public static DMatrix operator *(DMatrix A, DMatrix B) {
if (A.NbCols != B.NbRows) {
throw new Exception($"Cannot compute product, matrix dimensions do not match:\n\t" +
$"A : {A.NbRows}x{A.NbCols}\n\tB : {B.NbRows}x{B.NbCols}");
}
DMatrix res = new DMatrix(A.NbRows, B.NbCols);
for (int i = 0; i < res.NbRows; i++) {
for (int j = 0; j < res.NbCols; j++) {
for (int k = 0; k < A.NbCols; k++) {
res.data[i, j] += A.data[i, k] * B.data[k, j];
}
}
}
return res;
}
public DMatrix HadamardProduct(DMatrix A) {
if (NbCols != A.NbCols || NbRows != A.NbRows) {
throw new Exception($"Cannot compute Hadamard product," +
$" dimensions do not match:\n\tA : {NbRows}x{NbCols}\n\t" +
$"B : {A.NbRows}x{A.NbCols}");
}
DMatrix res = new DMatrix(NbRows, NbCols);
for (int i = 0; i < res.NbRows; i++) {
for (int j = 0; j < res.NbCols; j++) {
res.data[i, j] = data[i, j] * A.data[i, j];
}
}
return res;
}
public static DMatrix operator +(DMatrix A, double x) {
DMatrix res = new DMatrix(A.NbRows, A.NbCols);
for (int i = 0; i < A.NbRows; i++) {
for (int j = 0; j < A.NbCols; j++) {
res.data[i, j] = A.data[i, j] + x;
}
}
return res;
}
public static DMatrix operator -(DMatrix A, double x) {
return A + (-x);
}
public static DMatrix operator *(DMatrix A, double x) {
DMatrix res = new DMatrix(A.NbRows, A.NbCols);
for (int i = 0; i < A.NbRows; i++) {
for (int j = 0; j < A.NbCols; j++) {
res.data[i, j] = A.data[i, j] * x;
}
}
return res;
}
public double Trace() {
if (NbCols != NbRows) {
throw new Exception($"Cannot compute trace, matrix is not square:\n\t" +
$"A : {NbRows}x{NbCols}");
}
double res = 0;
for (int i = 0; i < NbRows; i++) {
res += data[i, i];
}
return res;
}
public double Det_recursive() {
if (NbCols != NbRows) {
throw new Exception($"Cannot compute determinant, matrix isn't square:\n\t" +
$"A : {NbRows}x{NbCols}");
}
double sum = 0;
if (NbRows == 1) {
return data[0, 0];
}
else if (NbRows == 2) {
return data[0, 0] * data[1, 1] - data[0, 1] * data[1, 0];
}
for (int N = 0; N < NbRows; N++) {
DMatrix minor = new DMatrix(NbRows - 1, NbRows - 1);
for (int i = 0; i < NbRows - 1; i++) {
for (int j = 0; j < NbRows; j++) {
if (j < N) {
minor.data[i, j] = data[i + 1, j];
}
else if (j > N) {
minor.data[i, j - 1] = data[i + 1, j];
}
}
}
int sgn = (N % 2 == 0) ? 1 : -1;
sum += sgn * data[0, N] * minor.Det_recursive();
}
return sum;
}
public double Norm_frobenius() {
double res = 0;
if (NbRows == 1 || NbCols == 1) {
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
res += data[i, j] * data[i, j];
}
}
}
else {
res = (~this * this).Trace();
}
return Math.Sqrt(res);
}
public static DMatrix Id(int n) {
DMatrix res = new DMatrix(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j) {
res.data[i, j] = 1;
}
else {
res.data[i, j] = 0;
}
}
}
return res;
}
public DMatrix Transpose() {
DMatrix res = new DMatrix(NbRows, NbCols);
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
data[i, j] = data[j, i];
}
}
return res;
}
public DMatrix Row(int row) {
DMatrix A = new DMatrix(1, NbCols);
for (int i = 0; i < NbCols; i++) {
A.data[0, i] = data[row, i];
}
return A;
}
public DMatrix Col(int col) {
DMatrix A = new DMatrix(NbRows, 1);
for (int i = 0; i < NbRows; i++) {
A.data[i, 0] = data[i, col];
}
return A;
}
public DMatrix Diag() {
if (NbRows != NbCols) {
throw new Exception($"Cannot compute diagonal, matrix isn't square:\n\t" +
$"A : {NbRows}x{NbCols}");
}
DMatrix res = new DMatrix(NbRows, NbCols);
for (int i = 0; i < NbRows; i++) {
res.data[i, i] = data[i, i];
}
return res;
}
public DMatrix Map(OneVarFunction func) {
DMatrix res = new DMatrix(NbRows, NbCols);
for (int i = 0; i < res.NbRows; i++) {
for (int j = 0; j < res.NbCols; j++) {
res.data[i, j] = func(data[i, j]);
}
}
return res;
}
public double[,] ToArray() {
double[,] arr = new double[NbRows, NbCols];
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
arr[i, j] = data[i, j];
}
}
return arr;
}
public string ToCsv() {
string str = "";
for (int i = 0; i < NbRows; i++) {
for (int j = 0; j < NbCols; j++) {
if (j != NbCols - 1) {
str += $"{data[i, j]},";
}
else {
str += $"{data[i, j]}\n";
}
}
}
return $"{str}\n";
}
public override string ToString() {
string str = "";
for (int i = 0; i < NbRows; i++) {
str += "[ ";
for (int j = 0; j < NbCols; j++) {
string decimals = new string('0', Precision);
string nb = data[i, j].ToString($"+0.{decimals};-0.{decimals};0")
.Replace('+', ' ');
if (j != NbCols - 1) {
str += nb + " ";
}
else {
str += nb + " ]\n";
}
}
}
return str + '\n';
}
}
}
Utils.cs
using System;
namespace Mathlib.Misc {
public static class Utils {
private static int seed;
private static Random rng;
public static void Seed(int s) {
seed = s;
rng = new Random(s);
}
public static int Seed() {
return seed;
}
static Utils() {
Seed((int) DateTime.Now.Ticks);
rng = new Random(seed);
}
public static double Rand() {
return rng.NextDouble();
}
public static int Rand(int max) {
return (int) (rng.NextDouble() * max);
}
public static float Rand(float max) {
return (float) (rng.NextDouble() * max);
}
public static double Rand(double max) {
return rng.NextDouble() * max;
}
public static int Rand(int min, int max) {
return (int) (rng.NextDouble() * (max - min) + min);
}
public static float Rand(float min, float max) {
return (float) (rng.NextDouble() * (max - min) + min);
}
public static double Rand(double min, double max) {
return rng.NextDouble() * (max - min) + min;
}
public static float Lerp(float val, float x1, float x2, float y1, float y2) {
if (val == x1) {
return y1;
}
if (val == x2) {
return y2;
}
return (y2 - y1) / (x2 - x1) * (val - x1) + y1;
}
public static double Lerp(double val, double x1, double x2, double y1, double y2) {
if (val == x1) {
return y1;
}
if (val == x2) {
return y2;
}
return (y2 - y1) / (x2 - x1) * (val - x1) + y1;
}
}
}
```
Utils
static (?) class, feel free to edit your post to add more details and context (e.g. expand a bit on the functionality, the purpose of the code you present to the reviewers).. don't worry about making a long post, there's no need to be concise on CR (character limit is double that of SO for a reason!) - a small paragraph with a code block forMathlib.Misc.Utils
could turn out pretty interesting... alas, only the code that's in the post is reviewable ;-) \$\endgroup\$