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