aboutsummaryrefslogtreecommitdiff
path: root/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Graph.java
blob: d4355312010b352961aec852e7cce4e04067c431 (plain)
  1. // SPDX-FileCopyrightText: 2025 Jonas Smedegaard <dr@jones.dk>
  2. // SPDX-License-Identifier: GPL-3.0-or-later
  3. package dk.biks.bachelorizer;
  4. import java.util.ArrayList;
  5. import java.util.Collection;
  6. import java.util.Collections;
  7. import java.util.HashSet;
  8. import java.util.List;
  9. import java.util.Set;
  10. import java.util.stream.Collectors;
  11. import com.example.portfolio3.AdjListGraph;
  12. import com.example.portfolio3.AbstractGraph;
  13. import com.example.portfolio3.GraphAlgorithms;
  14. import com.example.portfolio3.MatrixGraph;
  15. import com.example.portfolio3.Vertex;
  16. /// Bachelorizer - database model
  17. ///
  18. /// Slurps and parses data from upstream-provided comma-separated file.
  19. ///
  20. /// @version 0.0.1
  21. /// @see <https://moodle.ruc.dk/mod/assign/view.php?id=523186>
  22. public final class Graph {
  23. /// data about combinations as a Graph
  24. private AbstractGraph g;
  25. /// Default constructor
  26. ///
  27. /// @param path path to data file
  28. private Graph(final String path) {
  29. // read into temporary graph to resolve vertice count
  30. //
  31. // use Adjacency List Representation:
  32. // * cheap to bootstrap (done once)
  33. // * simple to count vertices (done once): O(1)
  34. AbstractGraph _g = new AdjListGraph();
  35. GraphAlgorithms.readGraph(_g, path);
  36. Integer _vertice_count = _g.vertices().size();
  37. // read into final graph
  38. //
  39. // use Adjacency Matrix Representation:
  40. // * expensive to bootstrap (done once)
  41. // * simple to add edges but needs vertice count
  42. // * simple to get edges (done repeatedly): O(1)
  43. //
  44. // TODO: avoid reading and parsing file twice
  45. AbstractGraph g = new MatrixGraph(_vertice_count);
  46. GraphAlgorithms.readGraph(g, path);
  47. System.out.println("\n\nGraph of choices constructed:");
  48. GraphAlgorithms.printGraph(g);
  49. // ensure the graph is connected
  50. assertConnected(g);
  51. System.out.println(
  52. "\n\nGraph is connected"
  53. + " (otherwise an exception was thrown)");
  54. // collect disjoint choice sets
  55. ArrayList<Set<Vertex>> s = disjoint(g);
  56. System.out.printf(
  57. "\n\n%d disjoint choice sets collected:\n",
  58. s.size());
  59. for (Set<Vertex> verticeSet: s) {
  60. System.out.print(
  61. "set(" + verticeSet.size() + "):");
  62. for (Vertex v: GraphAlgorithms.sortVertex(
  63. verticeSet)
  64. ) {
  65. System.out.print(" " + v.toString());
  66. }
  67. System.out.println();
  68. }
  69. // construct graph of disjoint choices
  70. AbstractGraph h = moduleGroups(s, g);
  71. System.out.println(
  72. "\n\nGraph of disjoint choices constructed:");
  73. GraphAlgorithms.printGraph(h);
  74. // find path through disjoint choice graph
  75. System.out.println(
  76. "\n\nSolution with disjoint choices found:");
  77. System.out.println(
  78. solution(h));
  79. // release temporary variables for garbage collection
  80. _g = null;
  81. _vertice_count = null;
  82. }
  83. /// JVM entry point
  84. ///
  85. /// @param args command-line arguments
  86. public static void main(final String[] args) {
  87. // first argument, if provided, is the data file path;
  88. // else use upstream named file in current directory.
  89. String path = (args.length > 0)
  90. ? args[0]
  91. : "combi.txt";
  92. Graph combi = new Graph(path);
  93. }
  94. /// utility function to check that a graph is connected
  95. ///
  96. /// If check fails, throw an unchecked exception,
  97. /// since it occurs at runtime and is unrecoverable.
  98. ///
  99. /// Time complexity of the operation is O(n²)
  100. /// where n is the amount of vertices,
  101. /// since visitDepthFirst() recurses out-edges of all vertices.
  102. ///
  103. /// @param g Graph to inspect
  104. /// @throws IllegalArgumentException
  105. /// https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
  106. public static void assertConnected(final AbstractGraph g) {
  107. // collect all vertices in the graph
  108. Collection<Vertex> c = g.vertices();
  109. // pick a random vertice in the graph
  110. Vertex v = g.vertices().iterator().next();
  111. // collect the set of visitable vertices
  112. Set<Vertex> visited = GraphAlgorithms.visitDepthFirst(
  113. g, v);
  114. // throw exception if not all vertices were visited
  115. if (visited.size() != c.size()) {
  116. throw new IllegalArgumentException(
  117. "graph is not connected");
  118. }
  119. }
  120. /// sets of disjoint choices
  121. ///
  122. /// @param g Graph to inspect
  123. /// @return list of disjoint sets
  124. public static ArrayList<Set<Vertex>> disjoint(
  125. final AbstractGraph g
  126. ) {
  127. // get all subject modules
  128. //
  129. // TODO: optionally seed this, to enable ranking control
  130. List<Vertex> modules = new ArrayList<>(g.vertices());
  131. Collections.shuffle(modules);
  132. return disjoint(g, modules);
  133. }
  134. /// groups of disjoint choices seeded by priority list of choices
  135. ///
  136. /// @param g Graph to inspect
  137. /// @param vip Ordered list of subject modules to prioritize
  138. /// @return List of sets of disjoint choices
  139. public static ArrayList<Set<Vertex>> disjoint(
  140. final AbstractGraph g, final List<Vertex> vip
  141. ) {
  142. ArrayList<Set<Vertex>> sets = new ArrayList<>();
  143. // track done subject modules as extendable set
  144. //
  145. // HashList used for efficient addition and lookup
  146. Set<Vertex> done = new HashSet<>();
  147. // convert module list to a set
  148. //
  149. // defined once outside loop as a slight optimization
  150. Set<Vertex> all_set = new HashSet<>(vip);
  151. // iterate over subject modules
  152. for (Vertex current : vip) {
  153. if (done.contains(current)) {
  154. continue;
  155. }
  156. Set<Vertex> isolated = new HashSet<>();
  157. isolated.add(current);
  158. for (Vertex compared : vip) {
  159. // nested extra check
  160. if (done.contains(compared) || compared.equals(current)) {
  161. continue;
  162. }
  163. if (isolated.stream().allMatch(u -> g.getWeight(u,compared) == null)) {
  164. isolated.add(compared);
  165. }
  166. }
  167. /*
  168. // add set of current and any unconnected modules
  169. // TODO: try tighten to include current in loop
  170. Set<Vertex> isolated = all_set.stream()
  171. .filter(x -> {
  172. if (x == current) {
  173. return false;
  174. }
  175. Integer weight = g.getWeight(current, x);
  176. return weight == null
  177. && !done.contains(x);
  178. }).collect(Collectors.toSet());
  179. isolated.add(current);
  180. */
  181. // add as set and omit from future iterations
  182. sets.add(isolated);
  183. done.addAll(isolated);
  184. }
  185. return sets;
  186. }
  187. /// sum of students' selections as a graph
  188. ///
  189. /// @param sets list of disjoint choices
  190. /// @param g choices as weights in graph
  191. /// @return groups of disjoint choices as a graph
  192. public static AbstractGraph moduleGroups(
  193. final ArrayList<Set<Vertex>> sets, final AbstractGraph g
  194. ) {
  195. AbstractGraph h = new AdjListGraph();
  196. for (Set<Vertex> s: sets) {
  197. for (Set<Vertex> t: sets) {
  198. if (t == s) {
  199. continue;
  200. }
  201. int sum = 0;
  202. for (Vertex v: s) {
  203. // students with this choice
  204. for (Vertex w: t) {
  205. Integer weight =
  206. g.getWeight(v, w);
  207. if (weight != null) {
  208. sum += weight;
  209. }
  210. }
  211. }
  212. h.insertEdge(
  213. s.toString(), t.toString(), sum);
  214. }
  215. }
  216. return h;
  217. }
  218. /// groups of disjoint module choices
  219. ///
  220. /// @param g sets of disjoint choices as a graph
  221. /// @return amount of students in consecutive slots
  222. public static int solution(final AbstractGraph g) {
  223. // pick a random vertice in the graph
  224. Vertex v = g.vertices().iterator().next();
  225. return solution(g, v);
  226. }
  227. /// groups of disjoint module choices seeded by start vertex
  228. ///
  229. /// @param g groups of disjoint choices as a graph
  230. /// @param v seed vertex within graph
  231. /// @return amount of students in consecutive slots
  232. public static int solution(
  233. final AbstractGraph g, final Vertex v
  234. ) {
  235. return GraphAlgorithms.pathLength(
  236. GraphAlgorithms.dijkstra(g, v));
  237. }
  238. }