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