I'm in the process of building my Kotlin and general programming knowledge and trying to follow good practices while doing so. I am working on a project with some important matrix manipulation so I decided I would build my own Matrix class and define the logic myself.
I could have just built it two accept Doubles. But I wanted to build it to support any numeric type and even allow other classes that I define like fractions or complex numbers.
I also wanted to make the values of the matrix private so that you only access values through specific methods. mostly because that seems best practice development for future projects dealing with secure data.
The Issue: Now set up as an interface. still room for improvement. One thing I was struggling to do was to find a way to have a parameter for the interface, that stores the multiplicative identity (1) and one for the additive identity (0) that is implemented for each class. For some reason this didn't seem to work.
interface MyNumber<T> {
operator fun plus(other: T): MyNumber<T>
operator fun unaryMinus(): MyNumber<T>
operator fun minus(other: T): MyNumber<T>
operator fun times(other: T): MyNumber<T>
operator fun div(other: T): MyNumber<T>
operator fun compareTo(other: T): Int
}
class MyInt(val value: Int): MyNumber<MyInt> {
override fun plus(other: MyInt) = MyInt(value + other.value)
override fun unaryMinus() = MyInt(-value)
override fun minus(other: MyInt) = MyInt(value - other.value)
override fun times(other: MyInt) = MyInt(value * other.value)
override fun div(other: MyInt) = MyInt(value / other.value)
override fun toString() = value.toString()
override fun equals(other: Any?): Boolean {
return when{
other is MyInt -> value == other.value
other is Int -> value == other
else -> false
}
}
override fun compareTo(other: MyInt) = value.compareTo(other.value)
}
class MyLong(val value: Long): MyNumber<MyLong> {
override fun plus(other: MyLong) = MyLong(value + other.value)
override fun unaryMinus() = MyLong(-value)
override fun minus(other: MyLong) = MyLong(value - other.value)
override fun times(other: MyLong) = MyLong(value * other.value)
override fun div(other: MyLong) = MyLong(value / other.value)
override fun toString() = value.toString()
override fun equals(other: Any?): Boolean {
return when{
other is MyLong -> value == other.value
other is Long -> value == other
else -> false
}
}
override fun compareTo(other: MyLong) = value.compareTo(other.value)
}
class MyDouble(val value: Double): MyNumber<MyDouble> {
override fun plus(other: MyDouble) = MyDouble(value + other.value)
override fun unaryMinus() = MyDouble(-value)
override fun minus(other: MyDouble) = MyDouble(value - other.value)
override fun times(other: MyDouble) = MyDouble(value * other.value)
override fun div(other: MyDouble) = MyDouble(value / other.value)
override fun toString() = value.toString()
override fun equals(other: Any?): Boolean {
return when{
other is MyDouble -> value == other.value
other is Double -> value == other
else -> false
}
}
override fun compareTo(other: MyDouble) = value.compareTo(other.value)
}
class Fraction private constructor(val num: Long, val div: Long): MyNumber<Fraction> {
data class Builder(var numerator: Long = 0L, var divisor: Long = 1L) {
fun build(numerator: Long, divisor: Long): Fraction {
if (divisor == 0L) {throw ArithmeticException("Cannot divide by Zero")}
this.numerator = numerator
this.divisor = divisor
val gcd = gcd(numerator, divisor)
val sign = sign(divisor.toDouble()).toLong()
return Fraction(sign * numerator / gcd, sign * divisor / gcd)
}
}
val value = num.toDouble()/div
override fun plus(other: Fraction) =
Builder().build(num * other.div + div * other.num, div * other.div)
override fun unaryMinus() = Fraction(-num, div)
override fun minus(other: Fraction) = this + -other
override fun times(other: Fraction) =
Builder().build(num * other.num, div * other.div)
fun invert() = Builder().build(div, num)
override fun div(other: Fraction) = this * other.invert()
override fun toString() = "$num / $div"
override fun equals(other: Any?): Boolean {
return when{
other is Fraction -> num * other.div == div * other.num
other is Double -> value == other
else -> false
}
}
override fun compareTo(other: Fraction) = value.compareTo(other.value)
}
class Complex(val real: Double, val image: Double): MyNumber<Complex> {
val abs = sqrt(real * real + image * image)
override fun plus(other: Complex) = Complex(real + other.real, image + other.image)
override fun unaryMinus() = Complex(-real, -image)
override fun minus(other: Complex) = this + -other
operator fun times(scalar: Double) =
Complex(real * scalar, image * scalar)
operator fun div(scalar: Double) = this * (1/scalar)
override fun times(other: Complex) =
Complex(real * other.real - image * other.image, real * other.image + image * other.real)
fun conj() = Complex(real, -image)
fun invert() = this.conj() / (this * this.conj()).abs
override fun div(other: Complex) = this * other.invert()
override fun toString() = "$real + ${image} i"
override fun equals(other: Any?): Boolean {
return when{
other is Complex -> real == other.real && image == other.image
other is Double -> real == other && image == 0.0
else -> false
}
}
override fun compareTo(other: Complex) = real.compareTo(other.real)
}
@Suppress("UNCHECKED_CAST")
class Matrix<T: MyNumber<T>>(val width: Int, val height: Int, private val values: List<List<MyNumber<T>>>) {
data class Builder(var width: Int = 0, var height: Int = 0) {
fun parameters(width: Int, height: Int) {
this.width = width
this.height = height
}
fun <T: MyNumber<T>> build(data: List<T>): Matrix<T> {
val values = List(height) { j -> List(width) {
i -> data[(j * width + i) % data.size]
}}
return Matrix(width, height, values)
}
}
fun getRowOrNull(j: Int): List<MyNumber<T>>? {
if (j in 0 until height) {
return values[j]
}
return null
}
fun getIndexed(i: Int, j: Int): MyNumber<T> {
return values[j][i]
}
fun getColOrNull(i: Int): List<MyNumber<T>>? {
if (i in 0 until width) {
return List(height) { j -> values[j][i]}
}
return null
}
operator fun plus(other: Matrix<T>): Matrix<T>? {
if (width != other.width || height != other.height ) {
return null
}
val newData = List(height) { j -> List(width) {
i -> getIndexed(i, j).plus(other.getIndexed(i, j) as T)
}}
return Matrix(width, height, newData)
}
operator fun times(other: Matrix<T>): Matrix<T>? {
if (width != other.height) {
return null
}
val newData = List(height) { j -> List(other.width) {
i -> values[j].mapIndexed { k, v -> (v * other.values[k][i] as T) }
.reduce{acc, it -> acc + it as T }
}}
return Matrix(other.width, height, newData)
}
fun transpose(): Matrix<T> {
val newData = List(width) { i -> List(height) {
j -> values[j][i]
}}
return Matrix(height, width, newData)
}
fun getMinor(i: Int, j: Int): Matrix<T>? {
if (i in 0 until width && j in 0 until height) {
val newData = List(height - 1) { y ->
List(width - 1) { x ->
when {
x < i && y < j -> values[y][x]
x < i -> values[y + 1][x]
y < j -> values[y][x + 1]
else -> values[y + 1][x + 1]
}
}
}
return Matrix(width - 1, height - 1, newData)
}
return null
}/*
fun determinate(): T {
}
fun coFactor(i: Int, j: Int): T {
sign = if ((i + j) % 2 == 0) 1
}*/
override fun toString(): String {
var string = ""
for (j in 0 until height) {
string += getRowOrNull(j)?.toString() + "\n"
}
return string
}
}