aboutsummaryrefslogtreecommitdiff
path: root/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Graph.java
blob: 1048fb4b26646552cc591b157b48e730bdfa6749 (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.HashMap;
  9. import java.util.Map;
  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(
  63. "set(" + verticeSet.size() + "):");
  64. for (Vertex v: GraphAlgorithms.sortVertex(
  65. verticeSet)
  66. ) {
  67. System.out.print(" " + v.toString());
  68. }
  69. System.out.println();
  70. }
  71. // construct graph of disjoint choices
  72. AbstractGraph h = moduleGroups(s, g);
  73. System.out.println(
  74. "\n\nGraph of disjoint choices constructed:");
  75. GraphAlgorithms.printGraph(h);
  76. // find path through disjoint choice graph
  77. System.out.println(
  78. "\n\nSolution with disjoint choices found:");
  79. System.out.println(
  80. solution(h));
  81. // release temporary variables for garbage collection
  82. _g = null;
  83. _vertice_count = null;
  84. }
  85. /// JVM entry point
  86. ///
  87. /// @param args command-line arguments
  88. public static void main(final String[] args) {
  89. // first argument, if provided, is the data file path;
  90. // else use upstream named file in current directory.
  91. String path = (args.length > 0)
  92. ? args[0]
  93. : "combi.txt";
  94. Graph combi = new Graph(path);
  95. }
  96. /// utility function to check that a graph is connected
  97. ///
  98. /// If check fails, throw an unchecked exception,
  99. /// since it occurs at runtime and is unrecoverable.
  100. ///
  101. /// Time complexity of the operation is O(n²)
  102. /// where n is the amount of vertices,
  103. /// since visitDepthFirst() recurses out-edges of all vertices.
  104. ///
  105. /// @param g Graph to inspect
  106. /// @throws IllegalArgumentException
  107. /// https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
  108. public static void assertConnected(final AbstractGraph g) {
  109. // collect all vertices in the graph
  110. Collection<Vertex> c = g.vertices();
  111. // pick a random vertice in the graph
  112. Vertex v = g.vertices().iterator().next();
  113. // collect the set of visitable vertices
  114. Set<Vertex> visited = GraphAlgorithms.visitDepthFirst(
  115. g, v);
  116. // throw exception if not all vertices were visited
  117. if (visited.size() != c.size()) {
  118. throw new IllegalArgumentException(
  119. "graph is not connected");
  120. }
  121. }
  122. /// sets of disjoint choices
  123. ///
  124. /// @param g Graph to inspect
  125. /// @return list of disjoint sets
  126. public static ArrayList<Set<Vertex>> disjoint(
  127. final AbstractGraph g
  128. ) {
  129. // get all subject modules
  130. //
  131. // TODO: optionally seed this, to enable ranking control
  132. List<Vertex> modules = new ArrayList<>(g.vertices());
  133. Collections.shuffle(modules);
  134. return disjoint(g, modules);
  135. }
  136. /// groups of disjoint choices seeded by priority list of choices
  137. ///
  138. /// @param g Graph to inspect
  139. /// @param vip Ordered list of subject modules to prioritize
  140. /// @return List of sets of disjoint choices
  141. public static ArrayList<Set<Vertex>> disjoint(
  142. final AbstractGraph g, final List<Vertex> vip
  143. ) {
  144. ArrayList<Set<Vertex>> sets = new ArrayList<>();
  145. // track done subject modules as extendable set
  146. //
  147. // HashList used for efficient addition and lookup
  148. Set<Vertex> done = new HashSet<>();
  149. // convert module list to a set
  150. //
  151. // defined once outside loop as a slight optimization
  152. Set<Vertex> all_set = new HashSet<>(vip);
  153. // iterate over subject modules
  154. for (Vertex current : vip) {
  155. if (done.contains(current)) {
  156. continue;
  157. }
  158. // add set of current and any unconnected modules
  159. Set<Vertex> isolated = new HashSet<>();
  160. isolated.add(current);
  161. for (Vertex compared: vip) {
  162. // skip if already done
  163. if (compared.equals(current)
  164. || done.contains(compared)) {
  165. continue;
  166. }
  167. if (isolated.stream().allMatch(u ->
  168. g.getWeight(u,compared) == null)
  169. ) {
  170. isolated.add(compared);
  171. }
  172. }
  173. // add as set and omit from future iterations
  174. sets.add(isolated);
  175. done.addAll(isolated);
  176. }
  177. return sets;
  178. }
  179. /// sum of students' selections as a graph
  180. ///
  181. /// @param sets list of disjoint choices
  182. /// @param g choices as weights in graph
  183. /// @return groups of disjoint choices as a graph
  184. public static AbstractGraph moduleGroups(
  185. final ArrayList<Set<Vertex>> sets, final AbstractGraph g
  186. ) {
  187. AbstractGraph h = new AdjListGraph();
  188. Map<Set<Vertex>, String> groupLabel = new HashMap<>();
  189. for (Set<Vertex> groupSet : sets) {
  190. // stringify each group of module selections
  191. String name = groupSet.stream()
  192. .map(Vertex::toString)
  193. // avoid differently sorted duplicates
  194. .sorted()
  195. // formatting of groups as vertices
  196. .collect(Collectors.joining(
  197. ", ", "{", "}"));
  198. // map group to name of group
  199. groupLabel.put(groupSet, name);
  200. }
  201. for (Set<Vertex> s: sets) {
  202. for (Set<Vertex> t: sets) {
  203. if (t == s) {
  204. continue;
  205. }
  206. // process each pair only once
  207. if (s.hashCode() > t.hashCode()) {
  208. continue;
  209. }
  210. // accumulate student count across set
  211. int sum = 0;
  212. for (Vertex v: s) {
  213. // students with this choice
  214. for (Vertex w: t) {
  215. Integer weight =
  216. g.getWeight(v, w);
  217. if (weight != null) {
  218. sum += weight;
  219. }
  220. }
  221. }
  222. // ensure h is treated as undirected
  223. h.insertEdge(
  224. groupLabel.get(s),
  225. groupLabel.get(t),
  226. sum);
  227. h.insertEdge(
  228. groupLabel.get(t),
  229. groupLabel.get(s),
  230. sum);
  231. }
  232. }
  233. return h;
  234. }
  235. /// groups of disjoint module choices
  236. ///
  237. /// @param g sets of disjoint choices as a graph
  238. /// @return amount of students in consecutive slots
  239. public static int solution(final AbstractGraph g) {
  240. List<Vertex> vertices = new ArrayList<>(g.vertices());
  241. int bestPathCost = Integer.MAX_VALUE; // number higher than total students
  242. for (int i = 0; i < 100; i++) {
  243. Collections.shuffle(vertices); // order represents path
  244. int cost = pathCost(g, vertices);
  245. if (cost < bestPathCost) {
  246. bestPathCost = cost;
  247. }
  248. }
  249. return bestPathCost;
  250. // pick a random vertice in the graph
  251. // Vertex v = g.vertices().iterator().next();
  252. // return solution(g, path);
  253. }
  254. private static int pathCost(AbstractGraph g, List<Vertex> path) {
  255. int cost = 0;
  256. for (int i = 0; i < path.size() - 1; i++) {
  257. cost += g.getWeight(path.get(i), path.get(i + 1));
  258. }
  259. return cost;
  260. }
  261. /// groups of disjoint module choices seeded by start vertex
  262. ///
  263. /// @param g groups of disjoint choices as a graph
  264. /// @param v seed vertex within graph
  265. /// @return amount of students in consecutive slots
  266. public static int solution(
  267. final AbstractGraph g, final Vertex v
  268. ) {
  269. return GraphAlgorithms.pathLength(
  270. GraphAlgorithms.dijkstra(g, v));
  271. }
  272. }