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