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