aboutsummaryrefslogtreecommitdiff
path: root/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Graph.java
blob: 0f5503ec32318a441fc5566bc8955d59f6e85c73 (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("set(" + verticeSet.size() + "):");
  61. for (Vertex v: GraphAlgorithms.sortVertex(verticeSet)) {
  62. System.out.print(" " + v.toString());
  63. }
  64. System.out.println();
  65. }
  66. // construct graph of disjoint choices
  67. AbstractGraph h = moduleGroups(s, g);
  68. System.out.println(
  69. "\n\nGraph of disjoint choices constructed:");
  70. GraphAlgorithms.printGraph(h);
  71. // find path through disjoint choice graph
  72. System.out.println(
  73. "\n\nSolution with disjoint choices found:");
  74. System.out.println(
  75. solution(h));
  76. // release temporary variables for garbage collection
  77. _g = null;
  78. _vertice_count = null;
  79. }
  80. /// JVM entry point
  81. ///
  82. /// @param args command-line arguments
  83. public static void main(final String[] args) {
  84. // first argument, if provided, is the data file path;
  85. // else use upstream named file in current directory.
  86. String path = (args.length > 0)
  87. ? args[0]
  88. : "combi.txt";
  89. Graph combi = new Graph(path);
  90. }
  91. /// utility function to check that a graph is connected
  92. ///
  93. /// If check fails, throw an unchecked exception,
  94. /// since it occurs at runtime and is unrecoverable.
  95. ///
  96. /// Time complexity of the operation is O(n²)
  97. /// where n is the amount of vertices,
  98. /// since visitDepthFirst() recurses out-edges of all vertices.
  99. ///
  100. /// @param g Graph to inspect
  101. /// @throws IllegalArgumentException
  102. /// https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
  103. public static void assertConnected(final AbstractGraph g) {
  104. // collect all vertices in the graph
  105. Collection<Vertex> c = g.vertices();
  106. // pick a random vertice in the graph
  107. Vertex v = g.vertices().iterator().next();
  108. // collect the set of visitable vertices
  109. Set<Vertex> visited = GraphAlgorithms.visitDepthFirst(
  110. g, v);
  111. // throw exception if not all vertices were visited
  112. if (visited.size() != c.size()) {
  113. throw new IllegalArgumentException(
  114. "graph is not connected");
  115. }
  116. }
  117. /// sets of disjoint choices
  118. ///
  119. /// @param g Graph to inspect
  120. /// @return list of disjoint sets
  121. public static ArrayList<Set<Vertex>> disjoint(final AbstractGraph g) {
  122. // get all subject modules
  123. //
  124. // TODO: optionally seed this, to enable ranking control
  125. List<Vertex> modules = new ArrayList<>(g.vertices());
  126. Collections.shuffle(modules);
  127. return disjoint(g, modules);
  128. }
  129. /// groups of disjoint choices seeded by priority list of choices
  130. ///
  131. /// @param g Graph to inspect
  132. /// @param vip Ordered list of subject modules to prioritize
  133. /// @return List of sets of disjoint choices
  134. public static ArrayList<Set<Vertex>> disjoint(final AbstractGraph g, final List<Vertex> vip) {
  135. ArrayList<Set<Vertex>> sets = new ArrayList<>();
  136. // track done subject modules as extendable set
  137. //
  138. // HashList used for efficient addition and lookup
  139. Set<Vertex> done = new HashSet<>();
  140. // convert module list to a set
  141. //
  142. // defined once outside loop as a slight optimization
  143. Set<Vertex> all_set = new HashSet<>(vip);
  144. // iterate over subject modules
  145. for (Vertex current : vip) {
  146. if (done.contains(current)) {
  147. continue;
  148. }
  149. // add set of current and any unconnected modules
  150. // TODO: try tighten to include current in loop
  151. Set<Vertex> isolated = all_set.stream()
  152. .filter(x ->
  153. {
  154. if (x == current) {
  155. return false;
  156. }
  157. Integer weight = g.getWeight(current, x);
  158. return weight == null
  159. && !done.contains(x);
  160. }).collect(Collectors.toSet());
  161. isolated.add(current);
  162. // add as set and omit from future iterations
  163. sets.add(isolated);
  164. done.addAll(isolated);
  165. }
  166. return sets;
  167. }
  168. /// sum of students' selections as a graph
  169. ///
  170. /// @param sets list of disjoint choices
  171. /// @param g choices as weights in graph
  172. /// @return groups of disjoint choices as a graph
  173. public static AbstractGraph moduleGroups(final ArrayList<Set<Vertex>> sets, final AbstractGraph g) {
  174. AbstractGraph h = new AdjListGraph();
  175. for (Set<Vertex> s: sets) {
  176. for (Set<Vertex> t: sets) {
  177. if (t == s) {
  178. continue;
  179. }
  180. int sum = 0;
  181. for (Vertex v: s) {
  182. // get num of students with this combination of modules.
  183. for (Vertex w: t) {
  184. Integer weight = g.getWeight(v, w);
  185. if (weight != null) {
  186. sum += weight;
  187. }
  188. }
  189. }
  190. h.insertEdge(s.toString(), t.toString(), sum);
  191. }
  192. }
  193. return h;
  194. }
  195. /// groups of disjoint module choices
  196. ///
  197. /// @param g sets of disjoint choices as a graph
  198. /// @return amount of students in consecutive slots
  199. public static int solution(final AbstractGraph g) {
  200. // pick a random vertice in the graph
  201. Vertex v = g.vertices().iterator().next();
  202. return solution(g, v);
  203. }
  204. /// groups of disjoint module choices seeded by start vertex
  205. ///
  206. /// @param g groups of disjoint choices as a graph
  207. /// @param v seed vertex within graph
  208. /// @return amount of students in consecutive slots
  209. public static int solution(final AbstractGraph g, final Vertex v) {
  210. return GraphAlgorithms.pathLength(
  211. GraphAlgorithms.dijkstra(g, v));
  212. }
  213. }