Skip to content

Commit

Permalink
first linear algebra equation solving helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
norganos committed Dec 13, 2024
1 parent 2660307 commit 93354a9
Show file tree
Hide file tree
Showing 8 changed files with 729 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.linkel.aoc.utils.geometry.plain.discrete

import de.linkel.aoc.utils.geometry.plain.continuous.VectorD
import de.linkel.aoc.utils.math.CommonMath
import kotlin.math.*

class Vector(
Expand Down Expand Up @@ -51,6 +52,15 @@ class Vector(
&& abs(this.deltaY) >= abs(other.deltaY)
}

fun min(): Vector {
return if (deltaX == 0) Vector(deltaX = 0, deltaY = deltaY.sign)
else if (deltaY == 0) Vector(deltaX = deltaX.sign, deltaY = 0)
else {
val gcd = CommonMath.gcd(deltaX, deltaY)
Vector(deltaX / gcd, deltaY / gcd)
}
}

fun turnClockwise(): Vector = Vector(-deltaY, deltaX)
fun turnCounterClockwise(): Vector = Vector(deltaY, -deltaX)
fun turnAround(): Vector = Vector(-deltaX, -deltaY)
Expand Down
23 changes: 21 additions & 2 deletions lib/src/main/kotlin/de/linkel/aoc/utils/grid/Grid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Grid<T: Any>(
dimension: Dimension = Dimension(1,1)
) {
companion object {
fun <T: Any> parse(lines: Sequence<String>, lambda: (pos: Point, c: Char) -> T?): Grid<T> {
fun <T: Any> parse(lines: Sequence<String>, crop: Boolean = true, lambda: (pos: Point, c: Char) -> T?): Grid<T> {
val grid = Grid<T>()
lines
.filter { it.isNotEmpty() }
Expand All @@ -27,7 +27,8 @@ class Grid<T: Any>(
}
}
}
grid.crop()
if (crop)
grid.crop()
return grid
}
}
Expand All @@ -39,6 +40,24 @@ class Grid<T: Any>(
height = dimension.height
)

fun straightLine(start: Point, direction: Vector): List<Point> {
val result = mutableListOf<Point>()
var pos = start
while (pos in boundingBox) {
result.add(pos)
pos += direction
}
return result
}

fun walkUntil(start: Point, vector: Vector, stop: (point: Point) -> Boolean): Point {
var pos = start
while (true) {
pos += vector
if (stop(pos)) return pos
}
}

// evtl nen performance-optimierteren zugriff? / ne liste aller belegten punkte pro row/col?
private val store = mutableMapOf<Point, T>()
val width get(): Int = boundingBox.width
Expand Down
22 changes: 22 additions & 0 deletions lib/src/main/kotlin/de/linkel/aoc/utils/math/algebra/Equations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.linkel.aoc.utils.math.algebra

import java.math.BigDecimal

object Equations {
fun cramer(coefficients: Matrix, results: List<BigDecimal>): List<BigDecimal>? {
require(coefficients.rows == coefficients.cols) { "coefficients matrix has to be square" }
require(results.size == coefficients.rows) { "results vector has to be same size as coefficients rows" }
val denominator = coefficients.det()
return (0 until coefficients.cols)
.map { i ->
coefficients.replaceCol(i, results).det()
}
.takeIf { numerators ->
numerators
.all { it % denominator == BigDecimal.ZERO}
}
?.map {
it / denominator
}
}
}
155 changes: 155 additions & 0 deletions lib/src/main/kotlin/de/linkel/aoc/utils/math/algebra/Matrix.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package de.linkel.aoc.utils.math.algebra

import java.math.BigDecimal

data class Matrix(
val cols: Int,
val rows: Int,
val values: List<BigDecimal>,
) {
init {
require(rows > 0) { "rows must be greater than zero" }
require(cols > 0) { "cols must be greater than zero" }
require(values.size == rows * cols) { "wrong number of values" }
}

fun det(): BigDecimal {
require(rows == cols) { "only square matrices can be determined" }
return when (rows) {
1 -> values[0]
2 -> values[0] * values[3] - values[1] * values[2]
else -> {
val withoutFirstCol = this.removeCol(0)
val minusOne = BigDecimal.ONE.negate()
(0 until rows)
.sumOf { idx ->
(minusOne.pow(idx)) * values[idx * cols] * withoutFirstCol.removeRow(idx).det()
}
}
}
}

fun getRow(idx: Int): List<BigDecimal> {
require(idx in 0 until rows) { "row index out of bounds" }
val select = (cols * idx) until (cols * (idx + 1))
return values.filterIndexed { i, _ -> i in select }
}

fun getCol(idx: Int): List<BigDecimal> {
require(idx in 0 until cols) { "col index out of bounds" }
return values.filterIndexed { i, _ -> i % cols == idx }
}

fun getAt(col: Int, row: Int): BigDecimal {
require(col in 0 until cols) { "col index out of bounds" }
require(row in 0 until rows) { "col index out of bounds" }
return values[row * cols + col]
}

fun removeRow(idx: Int): Matrix {
require(idx in 0 until rows) { "row index out of bounds" }
require(rows > 1) { "matrix must have more than one row" }
val remove = (cols * idx) until (cols * (idx + 1))
return Matrix(
cols = cols,
rows = rows - 1,
values = values.filterIndexed { i, _ -> i !in remove })
}

fun removeCol(idx: Int): Matrix {
require(idx in 0 until cols) { "col index out of bounds" }
require(cols > 1) { "matrix must have more than one col" }
return Matrix(
cols = cols - 1,
rows = rows,
values = values.filterIndexed { i, _ -> i % cols != idx }
)
}

fun insertRow(idx: Int, row: List<BigDecimal>): Matrix {
require(idx in 0 until rows+1) { "row index out of bounds" }
require(row.size == cols) { "wrong number of values" }
return Matrix(
cols = cols,
rows = rows + 1,
values = values.subList(0, idx * cols) + row + values.subList(idx * cols, values.size)
)
}

fun replaceRow(idx: Int, row: List<BigDecimal>): Matrix {
require(idx in 0 until rows) { "row index out of bounds" }
require(row.size == cols) { "wrong number of values" }
return Matrix(
cols = cols,
rows = rows,
values = values.subList(0, idx * cols) + row + values.subList((idx + 1) * cols, values.size)
)
}

fun insertCol(idx: Int, col: List<BigDecimal>): Matrix {
require(idx in 0 until cols+1) { "col index out of bounds" }
require(col.size == rows) { "wrong number of values" }
return Matrix(
cols = cols + 1,
rows = rows,
values = (0 until (cols + 1) * rows).map { i ->
val c = i % (cols + 1)
val r = i / (cols + 1)
if (c < idx)
values[r * cols + c]
else if (c == idx)
col[r]
else values[r * cols + c - 1]
}
)
}

fun replaceCol(idx: Int, col: List<BigDecimal>): Matrix {
require(idx in 0 until cols) { "col index out of bounds" }
require(col.size == rows) { "wrong number of values" }
return Matrix(
cols = cols,
rows = rows,
values = values.mapIndexed { i, v ->
val c = i % cols
val r = i / cols
if (c == idx)
col[r]
else
v
}
)
}

fun appendRow(row: List<BigDecimal>): Matrix {
require(row.size == cols) { "wrong number of values" }
return Matrix(
cols = cols,
rows = rows + 1,
values = values + row
)
}

fun appendCol(col: List<BigDecimal>): Matrix {
require(col.size == rows) { "wrong number of values" }
return Matrix(
cols = cols + 1,
rows = rows,
values = (0 until rows).flatMap { r ->
values.subList(r * cols, (r + 1) * cols) + col[r]
}
)
}

fun transpose(): Matrix {
return Matrix(
cols = rows,
rows = cols,
(0 until cols)
.flatMap { c ->
(0 until rows)
.map { r -> getAt(c, r) }
}
)
}
}
14 changes: 14 additions & 0 deletions lib/src/main/kotlin/de/linkel/aoc/utils/readers/ReaderMixIn.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.linkel.aoc.utils.readers

import java.io.Reader

fun Reader.charSequence(): Sequence<Char> {
val reader = this
return sequence {
while (true) {
val i = reader.read()
if (i >= 0) yield(i.toChar())
else break
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ class VectorTests {
Assertions.assertThat(vector1).isEqualTo(vector2)
Assertions.assertThat(vector1).isNotEqualTo(vector3)
}

@Test
fun `test Vector min method`() {
val vector1 = Vector(2, 3)
val vector2 = Vector(18, 6)
val vector3 = Vector(5, 0)
val vector4 = Vector(0, 2)
val vector5 = Vector(0, 0)
Assertions.assertThat(vector1.min()).isEqualTo(vector1)
Assertions.assertThat(vector2.min()).isEqualTo(Vector(3, 1))
Assertions.assertThat(vector3.min()).isEqualTo(Vector(1, 0))
Assertions.assertThat(vector4.min()).isEqualTo(Vector(0, 1))
Assertions.assertThat(vector5.min()).isEqualTo(Vector(0, 0))
}

@Test
fun `test Vector distance calculation`() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.linkel.aoc.utils.math.algebra

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test


class EquationTests {
private fun bd(vararg values: Int) = values.map { it.toBigDecimal() }.toList()

@Test
fun `simple 2x2 cramer`() {
Assertions.assertThat(
Equations.cramer(
Matrix(2, 2, bd(94, 22, 34, 67)),
bd(8400, 5400)
)
).isEqualTo(bd(80, 40))

Assertions.assertThat(
Equations.cramer(
Matrix(2, 2, bd(26, 67, 66, 21)),
bd(12748, 12176)
)
).isNull()
}
}
Loading

0 comments on commit 93354a9

Please sign in to comment.