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