|
1 | 1 | package eu.sim642.adventofcodelib |
2 | 2 |
|
| 3 | +import eu.sim642.adventofcodelib.IntegralImplicits.ExtraDivModIntegralOps |
| 4 | + |
3 | 5 | import scala.collection.mutable |
4 | 6 | import scala.math.Integral.Implicits.infixIntegralOps |
5 | 7 | import scala.math.Ordering.Implicits.infixOrderingOps |
6 | 8 | import scala.reflect.ClassTag |
| 9 | +import scala.util.boundary |
| 10 | +import scala.util.boundary.break |
7 | 11 |
|
| 12 | +/** |
| 13 | + * Integer-only Gaussian elimination. |
| 14 | + */ |
8 | 15 | object GaussianElimination { |
9 | 16 |
|
10 | 17 | case class Solution[A: Integral](dependentVars: Seq[Int], dependentGenerator: Seq[A], |
11 | 18 | freeVars: Seq[Int], freeGenerators: Seq[Seq[A]], |
12 | | - const: Seq[A]) { |
| 19 | + constGenerator: Seq[A]) { |
13 | 20 | private lazy val freeGeneratorsTransposed = { // transpose of empty generators needs right length for lazyZip to work below |
14 | 21 | if (freeGenerators.isEmpty) |
15 | | - const.map(_ => Nil) |
| 22 | + constGenerator.map(_ => Nil) |
16 | 23 | else |
17 | 24 | freeGenerators.transpose |
18 | 25 | } |
19 | 26 |
|
20 | | - require(dependentGenerator.size == const.size) |
| 27 | + require(dependentGenerator.size == constGenerator.size) |
21 | 28 | require(dependentGenerator.size == freeGeneratorsTransposed.size) |
22 | 29 |
|
23 | | - def evaluate(freeVals: Seq[A]): Seq[A] = { |
24 | | - (const lazyZip freeGeneratorsTransposed lazyZip dependentGenerator).map((v, fgt, mainVar) => { |
25 | | - val r = v - (fgt lazyZip freeVals).map(_ * _).sum |
26 | | - if (r % mainVar == 0) |
27 | | - r / mainVar |
28 | | - else |
29 | | - -summon[Integral[A]].one // TODO: Option |
30 | | - }) |
| 30 | + def evaluate(freeVals: Seq[A]): Option[Seq[A]] = { |
| 31 | + boundary { // poor man's sequence |
| 32 | + Some((constGenerator lazyZip freeGeneratorsTransposed lazyZip dependentGenerator).map((const, freeCoeffs, dependentCoeff) => { |
| 33 | + val r = const - (freeCoeffs lazyZip freeVals).map(_ * _).sum |
| 34 | + (r /! dependentCoeff).getOrElse(break(None)) |
| 35 | + })) |
| 36 | + } |
31 | 37 | } |
32 | 38 | } |
33 | 39 |
|
34 | | - def solve[A: ClassTag](initialA: Seq[Seq[A]], initialb: Seq[A])(using aIntegral: Integral[A]): Solution[A] = { |
| 40 | + def solve[A: ClassTag](initialA: Seq[Seq[A]], initialb: Seq[A])(using aIntegral: Integral[A]): Option[Solution[A]] = { |
35 | 41 | val m = initialA.size |
36 | 42 | val n = initialA.head.size |
37 | 43 | require(initialb.sizeIs == m) |
@@ -83,32 +89,36 @@ object GaussianElimination { |
83 | 89 | } |
84 | 90 | } |
85 | 91 |
|
86 | | - // check consistency |
87 | | - for (y2 <- y until b.size) |
88 | | - assert(b(y2) == 0) // TODO: return Option |
| 92 | + boundary { |
| 93 | + // check consistency |
| 94 | + for (y2 <- y until b.size) { |
| 95 | + if (b(y2) != 0) |
| 96 | + break(None) |
| 97 | + } |
89 | 98 |
|
90 | | - // backward elimination |
91 | | - val dependentVars = mutable.ArrayBuffer.empty[Int] |
92 | | - val freeVars = mutable.ArrayBuffer.empty[Int] |
93 | | - y = 0 |
94 | | - for (x <- 0 until n) { |
95 | | - if (y >= m || A(y)(x) == 0) |
96 | | - freeVars += x |
97 | | - else { |
98 | | - dependentVars += x |
99 | | - for (y2 <- 0 until y) |
100 | | - reduceRow(x, y, y2) |
101 | | - y += 1 |
| 99 | + // backward elimination |
| 100 | + val dependentVars = mutable.ArrayBuffer.empty[Int] |
| 101 | + val freeVars = mutable.ArrayBuffer.empty[Int] |
| 102 | + y = 0 |
| 103 | + for (x <- 0 until n) { |
| 104 | + if (y >= m || A(y)(x) == 0) |
| 105 | + freeVars += x |
| 106 | + else { |
| 107 | + dependentVars += x |
| 108 | + for (y2 <- 0 until y) |
| 109 | + reduceRow(x, y, y2) |
| 110 | + y += 1 |
| 111 | + } |
102 | 112 | } |
103 | | - } |
104 | 113 |
|
105 | | - val Aview = A.view.take(dependentVars.size) |
106 | | - Solution( |
107 | | - dependentVars = dependentVars.toSeq, |
108 | | - dependentGenerator = (A lazyZip dependentVars).map(_(_)).toSeq, |
109 | | - freeVars = freeVars.toSeq, |
110 | | - freeGenerators = freeVars.view.map(x => Aview.map(_(x)).toSeq).toSeq, |
111 | | - const = b.view.take(dependentVars.size).toSeq |
112 | | - ) |
| 114 | + val Aview = A.view.take(dependentVars.size) |
| 115 | + Some(Solution( |
| 116 | + dependentVars = dependentVars.toSeq, |
| 117 | + dependentGenerator = (A lazyZip dependentVars).map(_(_)).toSeq, |
| 118 | + freeVars = freeVars.toSeq, |
| 119 | + freeGenerators = freeVars.view.map(x => Aview.map(_(x)).toSeq).toSeq, |
| 120 | + constGenerator = b.view.take(dependentVars.size).toSeq |
| 121 | + )) |
| 122 | + } |
113 | 123 | } |
114 | 124 | } |
0 commit comments