KursblockungMatrix.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import { JavaObject, cast_java_lang_Object } from '../../java/lang/JavaObject';
  2. import { Random, cast_java_util_Random } from '../../java/util/Random';
  3. import { StringBuilder, cast_java_lang_StringBuilder } from '../../java/lang/StringBuilder';
  4. import { JavaLong, cast_java_lang_Long } from '../../java/lang/JavaLong';
  5. import { JavaString, cast_java_lang_String } from '../../java/lang/JavaString';
  6. import { Arrays, cast_java_util_Arrays } from '../../java/util/Arrays';
  7. import { System, cast_java_lang_System } from '../../java/lang/System';
  8. export class KursblockungMatrix extends JavaObject {
  9. private readonly RND : Random = new Random();
  10. private readonly matrix : Array<Array<number>>;
  11. private readonly rows : number;
  12. private readonly cols : number;
  13. private readonly permR : Array<number>;
  14. private readonly permC : Array<number>;
  15. private readonly r2c : Array<number>;
  16. private readonly c2r : Array<number>;
  17. private readonly besuchtR : Array<boolean>;
  18. private readonly abgearbeitetC : Array<boolean>;
  19. private readonly vorgaengerCzuR : Array<number>;
  20. private readonly queueR : Array<number>;
  21. private readonly potentialR : Array<number>;
  22. private readonly potentialC : Array<number>;
  23. private readonly distanzC : Array<number>;
  24. /**
  25. * Erzeugt eine neue Matrix mit {@code rows} Zeilen und {@code cols} Spalten.
  26. *
  27. * @param rows Die Anzahl der Zeilen der Matrix.
  28. * @param cols Die Anzahl der Spalten der Matrix.
  29. */
  30. public constructor(rows : number, cols : number) {
  31. super();
  32. this.rows = rows;
  33. this.cols = cols;
  34. this.matrix = [...Array(rows)].map(e => Array(cols).fill(0));
  35. this.permR = Array(rows).fill(0);
  36. this.permC = Array(cols).fill(0);
  37. this.r2c = Array(rows).fill(0);
  38. this.c2r = Array(cols).fill(0);
  39. this.besuchtR = Array(rows).fill(false);
  40. this.abgearbeitetC = Array(cols).fill(false);
  41. this.vorgaengerCzuR = Array(cols).fill(0);
  42. this.queueR = Array(rows).fill(0);
  43. this.potentialR = Array(rows).fill(0);
  44. this.potentialC = Array(cols).fill(0);
  45. this.distanzC = Array(cols).fill(0);
  46. KursblockungMatrix.initialisiere(this.permR);
  47. KursblockungMatrix.initialisiere(this.permC);
  48. }
  49. /**
  50. * Berechnet zur aktuellen Matrix ein maximales bipartites Matching. Die Methode
  51. * geht davon aus, dass in der Matrix ausschließlich die Werte 0 und 1
  52. * vorkommen. Werte ungleich 0 werden andernfalls als 1 (eine Kante im Graphen)
  53. * interpretiert. Nichtquadratische Matrizen sind erlaubt. Das Ergebnis der
  54. * Methode ist eine größtmögliche Zeilen- zu Spaltenzuordnung. Der Algorithmus
  55. * hat eine Laufzeit von O(n³).
  56. *
  57. * @param nichtdeterministisch definiert, ob das Ergebnis zufällig sein soll,
  58. * falls es mehrere optimale Lösungen gibt.
  59. *
  60. * @return die Zeilen- zu Spaltenzuordnung, negative Werte entsprechen einer
  61. * Nichtzuordnung.
  62. */
  63. public gibMaximalesBipartitesMatching(nichtdeterministisch : boolean) : Array<number> {
  64. Arrays.fill(this.r2c, -1);
  65. Arrays.fill(this.c2r, -1);
  66. this.initialisierPermRundPermC(nichtdeterministisch);
  67. for (let pseudoR : number = 0; pseudoR < this.rows; pseudoR++){
  68. let r : number = this.permR[pseudoR];
  69. Arrays.fill(this.besuchtR, false);
  70. Arrays.fill(this.vorgaengerCzuR, -1);
  71. let queue_first : number = 0;
  72. let queue_last : number = 0;
  73. this.queueR[queue_last] = r;
  74. queue_last++;
  75. this.besuchtR[r] = true;
  76. while (queue_first < queue_last) {
  77. let vonR : number = this.queueR[queue_first];
  78. queue_first++;
  79. for (let pseudoC : number = 0; pseudoC < this.cols; pseudoC++){
  80. let ueberC : number = this.permC[pseudoC];
  81. if ((this.matrix[vonR][ueberC] !== 0) && (this.r2c[vonR] !== ueberC)) {
  82. let zuR : number = this.c2r[ueberC];
  83. if (zuR === -1) {
  84. this.vorgaengerCzuR[ueberC] = vonR;
  85. for (let c2 : number = ueberC; c2 >= 0; ){
  86. let r2 : number = this.vorgaengerCzuR[c2];
  87. let saveC : number = this.r2c[r2];
  88. this.c2r[c2] = r2;
  89. this.r2c[r2] = c2;
  90. c2 = saveC;
  91. }
  92. queue_last = queue_first;
  93. break;
  94. }
  95. if (this.besuchtR[zuR] === false) {
  96. this.besuchtR[zuR] = true;
  97. this.queueR[queue_last] = zuR;
  98. queue_last++;
  99. this.vorgaengerCzuR[ueberC] = vonR;
  100. }
  101. }
  102. }
  103. }
  104. }
  105. return this.r2c;
  106. }
  107. /**
  108. * Berechnet zur aktuellen Matrix ein minimales gewichtetes Matching. Die
  109. * Methode geht davon aus, dass in der Matrix ganzzahlige Werte vorkommen, d.h.
  110. * es existiert eine Kante von jedem linken Knoten zu jedem rechten Knoten.
  111. * Negative Werte und nichtquadratische Matrizen sind erlaubt. Zur Berechnung
  112. * eines maximalen Matching kann man vorher alle Zellenwerte negieren. Das
  113. * Ergebnis der Methode ist eine Zeilen- zu Spaltenzuordnung, deren Summe
  114. * minimal ist. Der Algorithmus verwendet mehrere Runden eines SSSP-Algorithmus
  115. * (Dijkstra). Damit dies bei negativen Werten funktioniert, werden die Kanten
  116. * mit Hilfe von Knoten-Potentialen umgewichtet. Der Algorithmus hat eine
  117. * Laufzeit von O(n³).
  118. *
  119. * @see <a href= "https://en.wikipedia.org/wiki/Shortest_path_problem">Wikipedia
  120. * - Shortest_path_problem</a>
  121. *
  122. * @see <a href= "https://en.wikipedia.org/wiki/Johnson%27s_algorithm">Wikipedia
  123. * - Johnsons Algorithm</a>
  124. *
  125. * @param nichtdeterministisch definiert, ob das Ergebnis zufällig sein soll,
  126. * falls es mehrere optimale Lösungen gibt.
  127. *
  128. * @return die Zeilen- zu Spaltenzuordnung, negative Werte entsprechen einer
  129. * Nichtzuordnung.
  130. */
  131. public gibMinimalesBipartitesMatchingGewichtet(nichtdeterministisch : boolean) : Array<number> {
  132. Arrays.fill(this.r2c, -1);
  133. Arrays.fill(this.c2r, -1);
  134. Arrays.fill(this.potentialR, 0);
  135. Arrays.fill(this.potentialC, 0);
  136. this.initialisierPermRundPermC(nichtdeterministisch);
  137. if (this.rows <= this.cols) {
  138. for (let r : number = 0; r < this.rows; r++){
  139. let min : number = this.matrix[r][0] + this.potentialR[r] - this.potentialC[0];
  140. for (let c : number = 0; c < this.cols; c++){
  141. let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
  142. min = Math.min(min, kante);
  143. }
  144. this.potentialR[r] -= min;
  145. }
  146. }
  147. if (this.cols <= this.rows) {
  148. for (let c : number = 0; c < this.cols; c++){
  149. let min : number = this.matrix[0][c] + this.potentialR[0] - this.potentialC[c];
  150. for (let r : number = 0; r < this.rows; r++){
  151. let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
  152. min = Math.min(min, kante);
  153. }
  154. this.potentialC[c] += min;
  155. }
  156. }
  157. let dijkstraRunden : number = Math.min(this.rows, this.cols);
  158. Arrays.fill(this.abgearbeitetC, false);
  159. for (let ir : number = 0; ir < this.rows; ir++){
  160. let r : number = this.permR[ir];
  161. for (let ic : number = 0; ic < this.cols; ic++){
  162. let c : number = this.permC[ic];
  163. if (!this.abgearbeitetC[c]) {
  164. let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
  165. if (kante === 0) {
  166. this.r2c[r] = c;
  167. this.c2r[c] = r;
  168. this.abgearbeitetC[c] = true;
  169. dijkstraRunden--;
  170. break;
  171. }
  172. }
  173. }
  174. }
  175. for (let dijkstraRunde : number = 0; dijkstraRunde < dijkstraRunden; dijkstraRunde++){
  176. for (let ic : number = 0; ic < this.cols; ic++){
  177. let c : number = this.permC[ic];
  178. this.vorgaengerCzuR[c] = -1;
  179. this.abgearbeitetC[c] = false;
  180. this.distanzC[c] = Number.MAX_VALUE;
  181. for (let ir : number = 0; ir < this.rows; ir++){
  182. let r : number = this.permR[ir];
  183. if (this.r2c[r] < 0) {
  184. let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
  185. if (kante < this.distanzC[c]) {
  186. this.distanzC[c] = kante;
  187. this.vorgaengerCzuR[c] = r;
  188. }
  189. }
  190. }
  191. }
  192. let endknotenC : number = -1;
  193. for (let dijkstraZyklus : number = 0; dijkstraZyklus < this.cols; dijkstraZyklus++){
  194. let fromC : number = 0;
  195. for (let ic : number = 0; ic < this.cols; ic++){
  196. let c : number = this.permC[ic];
  197. if (!this.abgearbeitetC[c]) {
  198. if ((this.abgearbeitetC[fromC]) || (this.distanzC[c] < this.distanzC[fromC])) {
  199. fromC = c;
  200. }
  201. }
  202. }
  203. this.abgearbeitetC[fromC] = true;
  204. let overR : number = this.c2r[fromC];
  205. if (overR >= 0) {
  206. for (let ic : number = 0; ic < this.cols; ic++){
  207. let toC : number = this.permC[ic];
  208. let kante : number = this.matrix[overR][toC] + this.potentialR[overR] - this.potentialC[toC];
  209. let distance : number = this.distanzC[fromC] + kante;
  210. if (distance < this.distanzC[toC]) {
  211. this.distanzC[toC] = distance;
  212. this.vorgaengerCzuR[toC] = overR;
  213. }
  214. }
  215. } else {
  216. if (endknotenC < 0) {
  217. endknotenC = fromC;
  218. }
  219. }
  220. }
  221. let currentC : number = endknotenC;
  222. while (currentC >= 0) {
  223. let prevR : number = this.vorgaengerCzuR[currentC];
  224. let prevC : number = this.r2c[prevR];
  225. this.r2c[prevR] = currentC;
  226. this.c2r[currentC] = prevR;
  227. currentC = prevC;
  228. }
  229. for (let r : number = 0; r < this.rows; r++){
  230. let c : number = this.r2c[r];
  231. if (c >= 0) {
  232. let kante : number = this.matrix[r][c] + this.potentialR[r] - this.potentialC[c];
  233. this.potentialR[r] += this.distanzC[c] - kante;
  234. this.potentialC[c] += this.distanzC[c];
  235. }
  236. }
  237. }
  238. return this.r2c;
  239. }
  240. /**
  241. * Interne Methode zum Permutieren oder Initialisieren der Arrays
  242. * {@link KursblockungMatrix#permR} und {@link KursblockungMatrix#permC}.
  243. *
  244. * @param nichtdeterministisch falls {@code true} werden
  245. * {@link KursblockungMatrix#permR} und
  246. * {@link KursblockungMatrix#permC} permutiert,
  247. * sonst initialisiert.
  248. */
  249. private initialisierPermRundPermC(nichtdeterministisch : boolean) : void {
  250. if (nichtdeterministisch) {
  251. this.permutiere(this.permR);
  252. this.permutiere(this.permC);
  253. } else {
  254. KursblockungMatrix.initialisiere(this.permR);
  255. KursblockungMatrix.initialisiere(this.permC);
  256. }
  257. }
  258. /**
  259. * Interne Methode zum Initialisieren eines Arrays so, dass das Array mit den
  260. * Zahlen {@code 0,1,2...} gefüllt wird.
  261. *
  262. * @param perm Das Array, welches mit den Zahlen {@code 0,1,2...} gefüllt wird.
  263. */
  264. private static initialisiere(perm : Array<number>) : void {
  265. let laenge : number = perm.length;
  266. for (let i : number = 0; i < laenge; i++){
  267. perm[i] = i;
  268. }
  269. }
  270. /**
  271. * Interne Methode zum zufälligen Permutieren eines Arrays.
  272. *
  273. * @param perm Das Array, dessen Inhalt zufällig permutiert wird.
  274. */
  275. private permutiere(perm : Array<number>) : void {
  276. let laenge : number = perm.length;
  277. for (let i : number = 0; i < laenge; i++){
  278. let j : number = this.RND.nextInt(laenge);
  279. let saveI : number = perm[i];
  280. let saveJ : number = perm[j];
  281. perm[i] = saveJ;
  282. perm[j] = saveI;
  283. }
  284. }
  285. /**
  286. * Erlaubt Zugriff auf den Inhalt des Arrays.
  287. *
  288. * @return Die Array-Referenz.
  289. */
  290. public getMatrix() : Array<Array<number>> {
  291. return this.matrix;
  292. }
  293. /**
  294. * Erzeugt String-Ausgabe des Arrays sowie der Zeilen-zu-Spalten-Zuordnung
  295. * {@link KursblockungMatrix#r2c}. Diese Methode ist für Debug-Zwecke gedacht.
  296. *
  297. * @param kommentar Ein Kommentar der über der Matrix angezeigt wird.
  298. * @param zellenbreite Die Breite bei der Ausgabe der Zelle.
  299. * @param mitKnotenPotential Falls {@code true}, werden die Kantenwerte
  300. * umgewichtet entsprechenden der Knotenpotentiale,
  301. * andernfalls bleiben die Kantenwerte unverändert.
  302. *
  303. * @return Eine String-Representation der Matrix.
  304. */
  305. public convertToString(kommentar : String, zellenbreite : number, mitKnotenPotential : boolean) : String {
  306. let sb : StringBuilder = new StringBuilder();
  307. sb.append(kommentar.valueOf() + System.lineSeparator().valueOf());
  308. for (let r : number = 0; r < this.rows; r++){
  309. for (let c : number = 0; c < this.cols; c++){
  310. let wert : number = mitKnotenPotential ? this.matrix[r][c] + this.potentialR[r] - this.potentialC[c] : this.matrix[r][c];
  311. let sWert : String | null = "" + wert;
  312. while (sWert.length < zellenbreite)
  313. sWert = " " + sWert.valueOf();
  314. let sZusatz : String = this.r2c[r] === c ? "*" : " ";
  315. sb.append(sWert.valueOf() + sZusatz.valueOf());
  316. }
  317. sb.append("\n");
  318. }
  319. sb.append("r2c = " + Arrays.toString(this.r2c).valueOf());
  320. sb.append("\n");
  321. return sb.toString();
  322. }
  323. /**
  324. * Füllt die Matrix mit ganzzahligen zufälligen Zahlenwerten aus dem Intervall
  325. * {@code [von;bis]}.
  326. *
  327. * @param von Der kleinstmögliche zufällige Wert (inklusive).
  328. * @param bis Der größtmögliche zufällige Wert (inklusive).
  329. */
  330. public fuelleMitZufallszahlenVonBis(von : number, bis : number) : void {
  331. for (let r : number = 0; r < this.rows; r++){
  332. for (let c : number = 0; c < this.cols; c++){
  333. this.matrix[r][c] = this.RND.nextInt(bis - von + 1) + von;
  334. }
  335. }
  336. }
  337. isTranspiledInstanceOf(name : string): boolean {
  338. return ['de.nrw.schule.svws.core.kursblockung.KursblockungMatrix'].includes(name);
  339. }
  340. }
  341. export function cast_de_nrw_schule_svws_core_kursblockung_KursblockungMatrix(obj : unknown) : KursblockungMatrix {
  342. return obj as KursblockungMatrix;
  343. }