aboutsummaryrefslogtreecommitdiff
path: root/src/dk.biks.bachelorizer/dk/biks/bachelorizer/Combi.java
blob: e91c74ea00d341ece4507382ba10b151e7bcf7c5 (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.Iterator;
  9. import java.util.LinkedList;
  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.Graph;
  15. import com.example.portfolio3.GraphAlgorithms;
  16. import com.example.portfolio3.MatrixGraph;
  17. import com.example.portfolio3.Vertex;
  18. /// Combi - static sample dataset of course combinations
  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 Combi {
  25. /// data about combinations as a Graph
  26. private Graph g;
  27. /// Default constructor
  28. ///
  29. /// @param path path to data file
  30. private Combi(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. Graph _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. Graph g = new MatrixGraph(_vertice_count);
  48. GraphAlgorithms.readGraph(g, path);
  49. // ensure the graph is connected
  50. assertConnected(g);
  51. nonOverlapping(g);
  52. // GraphAlgorithms.printGraph(g);
  53. GraphAlgorithms.printGraph(moduleGroups(nonOverlapping(g), g));
  54. System.out.println(
  55. solution(moduleGroups(nonOverlapping(g), g)));
  56. // release temporary variables for garbage collection
  57. _g = null;
  58. _vertice_count = null;
  59. }
  60. /// JVM entry point
  61. ///
  62. /// @param args command-line arguments
  63. public static void main(final String[] args) {
  64. // first argument, if provided, is the data file path;
  65. // else use upstream named file in current directory.
  66. String path = (args.length > 0)
  67. ? args[0]
  68. : "combi.txt";
  69. Combi combi = new Combi(path);
  70. }
  71. /// utility function to check that a graph is connected
  72. ///
  73. /// If check fails, throw an unchecked exception,
  74. /// since it occurs at runtime and is unrecoverable.
  75. ///
  76. /// Time complexity of the operation is O(n²)
  77. /// where n is the amount of vertices,
  78. /// since visitDepthFirst() recurses out-edges of all vertices.
  79. ///
  80. /// @param g Graph to inspect
  81. /// @throws IllegalArgumentException
  82. /// https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
  83. public static void assertConnected(Graph g) {
  84. // collect all vertices in the graph
  85. Collection<Vertex> c = g.vertices();
  86. // pick a random vertice in the graph
  87. Vertex v = g.vertices().iterator().next();
  88. // collect the set of visitable vertices
  89. Set<Vertex> visited = GraphAlgorithms.visitDepthFirst(
  90. g, v);
  91. // throw exception if not all vertices were visited
  92. if (visited.size() != c.size()) {
  93. throw new IllegalArgumentException(
  94. "graph is not connected");
  95. }
  96. }
  97. /// groups of disjoint choices
  98. ///
  99. /// @param g Graph to inspect
  100. /// @return list of non-overlapping sets
  101. public static ArrayList<Set<Vertex>> nonOverlapping(Graph g) {
  102. // get all subject modules
  103. //
  104. // TODO: optionally seed this, to enable ranking control
  105. List<Vertex> modules = new ArrayList<>(g.vertices());
  106. Collections.shuffle(modules);
  107. return nonOverlapping(g, modules);
  108. }
  109. /// groups of disjoint choices seeded by priority list of choices
  110. ///
  111. /// @param g Graph to inspect
  112. /// @param vip Ordered list of subject modules to prioritize
  113. /// @return List of sets of disjoint choices
  114. public static ArrayList<Set<Vertex>> nonOverlapping(Graph g, List<Vertex> vip) {
  115. ArrayList<Set<Vertex>> sets = new ArrayList<>();
  116. // track done subject modules as extendable set
  117. //
  118. // HashList used for efficient addition and lookup
  119. Set<Vertex> done = new HashSet<>();
  120. // convert module list to a set
  121. //
  122. // defined once outside loop as a slight optimization
  123. Set<Vertex> all_set = new HashSet<>(vip);
  124. // iterate over subject modules
  125. for (Vertex current : vip) {
  126. if (done.contains(current)) {
  127. continue;
  128. }
  129. // add set of current and any unconnected modules
  130. // TODO: try tighten to include current in loop
  131. Set<Vertex> isolated = all_set.stream()
  132. .filter(x ->
  133. {
  134. if (x == current)
  135. return false;
  136. Integer weight = g.getWeight(current, x);
  137. return weight == null
  138. && !done.contains(x);
  139. }).collect(Collectors.toSet());
  140. isolated.add(current);
  141. // add as set and omit from future iterations
  142. sets.add(isolated);
  143. done.addAll(isolated);
  144. }
  145. for(Set<Vertex> s: sets) {
  146. System.out.println("set: "+s.size());
  147. for (Vertex v: s) {
  148. System.out.println(" "+v.toString());
  149. }
  150. }
  151. return sets;
  152. }
  153. /// sum of students' selections as a graph
  154. ///
  155. /// @param sets list of disjoint choices
  156. /// @param g choices as weights in graph
  157. /// @return groups of disjoint choices as a graph
  158. public static Graph moduleGroups(ArrayList<Set<Vertex>> sets, Graph g) {
  159. Graph h = new AdjListGraph();
  160. for (Set<Vertex> s: sets) {
  161. for (Set<Vertex> t: sets) {
  162. if (t == s) continue;
  163. int sum = 0;
  164. for (Vertex v: s) {
  165. // get num of students with this combination of modules.
  166. for (Vertex w: t) {
  167. Integer weight = g.getWeight(v, w);
  168. if (weight != null) {
  169. sum += weight;
  170. }
  171. }
  172. }
  173. h.insertEdge(s.toString(), t.toString(), sum);
  174. }
  175. }
  176. return h;
  177. }
  178. /// groups of disjoint module choices
  179. ///
  180. /// @param g groups of non-overlapping selections as a graph
  181. /// @return amount of students in consecutive slots
  182. public static int solution(Graph g) {
  183. // pick a random vertice in the graph
  184. Vertex v = g.vertices().iterator().next();
  185. return solution(g, v);
  186. }
  187. /// groups of disjoint module choices seeded by start vertex
  188. ///
  189. /// @param g groups of non-overlapping selections as a graph
  190. /// @param v seed vertex within graph
  191. /// @return amount of students in consecutive slots
  192. public static int solution(Graph g, Vertex v) {
  193. return GraphAlgorithms.pathLength(
  194. GraphAlgorithms.dijkstra(g, v));
  195. }
  196. }