diff --git a/lib/src/main/java/de/edux/math/Entity.java b/lib/src/main/java/de/edux/math/Entity.java new file mode 100644 index 0000000..2f1ceb0 --- /dev/null +++ b/lib/src/main/java/de/edux/math/Entity.java @@ -0,0 +1,13 @@ +package de.edux.math; + +public interface Entity { + + T add(T another); + + T subtract(T another); + + T multiply(T another); + + T scalarMultiply(double n); + +} diff --git a/lib/src/main/java/de/edux/math/MathUtil.java b/lib/src/main/java/de/edux/math/MathUtil.java new file mode 100644 index 0000000..162c1fc --- /dev/null +++ b/lib/src/main/java/de/edux/math/MathUtil.java @@ -0,0 +1,16 @@ +package de.edux.math; + +public final class MathUtil { + + public static double[] unwrap(double[][] matrix) { + double[] result = new double[matrix.length * matrix[0].length]; + int i = 0; + for (double[] arr : matrix) { + for (double val : arr) { + result[i++] = val; + } + } + return result; + } + +} diff --git a/lib/src/main/java/de/edux/math/Validations.java b/lib/src/main/java/de/edux/math/Validations.java new file mode 100644 index 0000000..7fbc8e8 --- /dev/null +++ b/lib/src/main/java/de/edux/math/Validations.java @@ -0,0 +1,17 @@ +package de.edux.math; + +public final class Validations { + + public static void size(double[] first, double[] second) { + if (first.length != second.length) { + throw new IllegalArgumentException("sizes mismatch"); + } + } + + public static void sizeMatrix(double[][] first, double[][] second) { + if (first.length != second.length || first[0].length != second[0].length) { + throw new IllegalArgumentException("sizes mismatch"); + } + } + +} diff --git a/lib/src/main/java/de/edux/math/entity/Matrix.java b/lib/src/main/java/de/edux/math/entity/Matrix.java new file mode 100644 index 0000000..c643a39 --- /dev/null +++ b/lib/src/main/java/de/edux/math/entity/Matrix.java @@ -0,0 +1,161 @@ +package de.edux.math.entity; + +import de.edux.math.Entity; +import de.edux.math.MathUtil; +import de.edux.math.Validations; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class Matrix implements Entity, Iterable { + + private final double[][] raw; + + public Matrix(double[][] matrix) { + this.raw = matrix; + } + + @Override + public Matrix add(Matrix another) { + return add(another.raw()); + } + + public Matrix add(double[][] another) { + Validations.sizeMatrix(raw, another); + + double[][] result = new double[raw.length][raw[0].length]; + + for (int i = 0; i < result.length; i++) { + for (int a = 0; a < result[0].length; a++) { + result[i][a] = raw[i][a] + another[i][a]; + } + } + + return new Matrix(result); + } + + @Override + public Matrix subtract(Matrix another) { + return subtract(another.raw()); + } + + @Override + public Matrix multiply(Matrix another) { + return multiply(another.raw()); + } + + public Matrix multiply(double[][] another) { + return null; // TODO optimized algorithm for matrix multiplication + } + + @Override + public Matrix scalarMultiply(double n) { + double[][] result = new double[raw.length][raw[0].length]; + + for (int i = 0; i < result.length; i++) { + for (int a = 0; a < result[0].length; a++) { + result[i][a] = raw[i][a] * n; + } + } + + return new Matrix(result); + } + + public Matrix subtract(double[][] another) { + Validations.sizeMatrix(raw, another); + + double[][] result = new double[raw.length][raw[0].length]; + + for (int i = 0; i < result.length; i++) { + for (int a = 0; a < result[0].length; a++) { + result[i][a] = raw[i][a] - another[i][a]; + } + } + + return new Matrix(result); + } + + public boolean isSquare() { + return rows() == columns(); + } + + public int rows() { + return raw.length; + } + + public int columns() { + return raw[0].length; + } + + public double[][] raw() { + return raw; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Matrix matrix) { + if (matrix.rows() != rows() || matrix.columns() != columns()) { + return false; + } + for (int i = 0; i < raw.length; i++) { + for (int a = 0; a < raw[i].length; a++) { + if (matrix.raw()[i][a] != raw[i][a]) { + return false; + } + } + } + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("[").append("\n"); + for (int i = 0; i < raw.length; i++) { + builder.append(" ").append("["); + for (int a = 0; a < raw[i].length; a++) { + builder.append(raw[i][a]); + if (a != raw[i].length - 1) { + builder.append(", "); + } + } + builder.append("]"); + if (i != raw.length - 1) { + builder.append(","); + } + builder.append("\n"); + } + return builder.append("]").toString(); + } + + @Override + public Iterator iterator() { + return new MatrixIterator(raw); + } + + public static class MatrixIterator implements Iterator { + + private final double[] data; + private int current; + + public MatrixIterator(double[][] data) { + this.data = MathUtil.unwrap(data); + this.current = 0; + } + + @Override + public boolean hasNext() { + return current < data.length; + } + + @Override + public Double next() { + if (!hasNext()) + throw new NoSuchElementException(); + return data[current++]; + } + + } + +} diff --git a/lib/src/main/java/de/edux/math/entity/Vector.java b/lib/src/main/java/de/edux/math/entity/Vector.java new file mode 100644 index 0000000..a7cf59d --- /dev/null +++ b/lib/src/main/java/de/edux/math/entity/Vector.java @@ -0,0 +1,154 @@ +package de.edux.math.entity; + +import de.edux.math.Entity; +import de.edux.math.Validations; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class Vector implements Entity, Iterable { + + private final double[] raw; + + public Vector(double[] vector) { + this.raw = vector; + } + + @Override + public Vector add(Vector another) { + return add(another.raw()); + } + + public Vector add(double[] another) { + Validations.size(raw, another); + + double[] result = new double[length()]; + for (int i = 0; i < result.length; i++) { + result[i] = raw[i] + another[i]; + } + + return new Vector(result); + } + + @Override + public Vector subtract(Vector another) { + return subtract(another.raw()); + } + + public Vector subtract(double[] another) { + Validations.size(raw, another); + + double[] result = new double[length()]; + for (int i = 0; i < result.length; i++) { + result[i] = raw[i] - another[i]; + } + + return new Vector(result); + } + + @Override + public Vector multiply(Vector another) { + return multiply(another.raw()); + } + + public Vector multiply(double[] another) { + Validations.size(raw, another); + + double[] result = new double[length()]; + for (int i = 0; i < result.length; i++) { + result[i] = raw[i] * another[i]; + if (result[i] == 0) { // Avoiding -0 result + result[i] = 0; + } + } + + return new Vector(result); + } + + @Override + public Vector scalarMultiply(double n) { + double[] result = new double[length()]; + for (int i = 0; i < result.length; i++) { + result[i] = raw[i] * n; + if (result[i] == 0) { // Avoiding -0 result + result[i] = 0; + } + } + + return new Vector(result); + } + + public double dot(Vector another) { + return dot(another.raw()); + } + + public double dot(double[] another) { + Validations.size(raw, another); + + double result = 0; + for (int i = 0; i < raw.length; i++) { + result += raw[i] * another[i]; + } + + return result; + } + + public int length() { + return raw.length; + } + + public double[] raw() { + return raw.clone(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Vector) { + return Arrays.equals(raw, ((Vector) obj).raw()); + } + return false; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("["); + for (int i = 0; i < raw.length; i++) { + builder.append(raw[i]); + if (i != raw.length - 1) { + builder.append(", "); + } + } + return builder.append("]").toString(); + } + + @Override + public Iterator iterator() { + return new VectorIterator(raw); + } + + public static class VectorIterator implements Iterator { + + private final double[] data; + private int current; + + public VectorIterator(double[] data) { + this.data = data; + this.current = 0; + } + + @Override + public boolean hasNext() { + return current < data.length; + } + + @Override + public Double next() { + if (!hasNext()) + throw new NoSuchElementException(); + return data[current++]; + } + + } + +} diff --git a/lib/src/test/java/de/edux/math/entity/MatrixTest.java b/lib/src/test/java/de/edux/math/entity/MatrixTest.java new file mode 100644 index 0000000..ed590df --- /dev/null +++ b/lib/src/test/java/de/edux/math/entity/MatrixTest.java @@ -0,0 +1,59 @@ +package de.edux.math.entity; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MatrixTest { + + static Matrix first; + static Matrix second; + + @BeforeEach + public void init() { + first = new Matrix(new double[][] { + {5, 3, -1}, + {-2, 0, 6}, + {5, 1, -9} + }); + second = new Matrix(new double[][] { + {8, 7, 4}, + {1, -5, 2}, + {0, 3, 0} + }); + } + + @Test + public void testAdd() { + assertEquals(new Matrix(new double[][] { + {13, 10, 3}, + {-1, -5, 8}, + {5, 4, -9} + }), first.add(second)); + } + + @Test + public void testSubtract() { + assertEquals(new Matrix(new double[][] { + {-3, -4, -5}, + {-3, 5, 4}, + {5, -2, -9} + }), first.subtract(second)); + } + + @Test + public void testScalarMultiply() { + assertEquals(new Matrix(new double[][] { + {20, 12, -4}, + {-8, 0, 24}, + {20, 4, -36} + }), first.scalarMultiply(4)); + assertEquals(new Matrix(new double[][] { + {-48, -42, -24}, + {-6, 30, -12}, + {0, -18, 0} + }), second.scalarMultiply(-6)); + } + +} diff --git a/lib/src/test/java/de/edux/math/entity/VectorTest.java b/lib/src/test/java/de/edux/math/entity/VectorTest.java new file mode 100644 index 0000000..f4fc013 --- /dev/null +++ b/lib/src/test/java/de/edux/math/entity/VectorTest.java @@ -0,0 +1,45 @@ +package de.edux.math.entity; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VectorTest { + + static Vector first; + static Vector second; + + @BeforeAll + public static void init() { + first = new Vector(new double[] {1, 5, 4}); + second = new Vector(new double[] {3, 8, 0}); + } + + @Test + public void testAdd() { + assertEquals(new Vector(new double[] {4, 13, 4}), first.add(second)); + } + + @Test + public void testSubtract() { + assertEquals(new Vector(new double[] {-2, -3, 4}), first.subtract(second)); + } + + @Test + public void testMultiply() { + assertEquals(new Vector(new double[] {3, 40, 0}), first.multiply(second)); + } + + @Test + public void testScalarMultiply() { + assertEquals(new Vector(new double[] {3, 15, 12}), first.scalarMultiply(3)); // first by 3 + assertEquals(new Vector(new double[] {-6, -16, 0}), second.scalarMultiply(-2)); // second by -2 + } + + @Test + public void testDot() { + assertEquals(43, first.dot(second)); + } + +}