// SPDX-FileCopyrightText: 2025 Jonas Smedegaard // SPDX-License-Identifier: GPL-3.0-or-later package dk.biks.bachelorizer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import com.example.portfolio3.AdjListGraph; import com.example.portfolio3.Graph; import com.example.portfolio3.GraphAlgorithms; import com.example.portfolio3.MatrixGraph; import com.example.portfolio3.Vertex; /// Combi - static sample dataset of course combinations /// /// Slurps and parses data from upstream-provided comma-separated file. /// /// @version 0.0.1 /// @see public final class Combi { /// data about combinations as a Graph private Graph g; /// Default constructor /// /// @param path path to data file private Combi(final String path) { // read into temporary graph to resolve vertice count // // use Adjacency List Representation: // * cheap to bootstrap (done once) // * simple to count vertices (done once): O(1) Graph _g = new AdjListGraph(); GraphAlgorithms.readGraph(_g, path); Integer _vertice_count = _g.vertices().size(); // read into final graph // // use Adjacency Matrix Representation: // * expensive to bootstrap (done once) // * simple to add edges but needs vertice count // * simple to get edges (done repeatedly): O(1) // // TODO: avoid reading and parsing file twice Graph g = new MatrixGraph(_vertice_count); GraphAlgorithms.readGraph(g, path); // ensure the graph is connected assertConnected(g); nonOverlapping(g); // GraphAlgorithms.printGraph(g); // release temporary variables for garbage collection _g = null; _vertice_count = null; } /// JVM entry point /// /// @param args command-line arguments public static void main(final String[] args) { // first argument, if provided, is the data file path; // else use upstream named file in current directory. String path = (args.length > 0) ? args[0] : "combi.txt"; Combi combi = new Combi(path); } /// utility function to check that a graph is connected /// /// If check fails, throw an unchecked exception, /// since it occurs at runtime and is unrecoverable. /// /// Time complexity of the operation is O(n²) /// where n is the amount of vertices, /// since visitDepthFirst() recurses out-edges of all vertices. /// /// @param g Graph to inspect /// @throws IllegalArgumentException /// https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html public static void assertConnected(Graph g) { // collect all vertices in the graph Collection c = g.vertices(); // pick a random vertice in the graph Vertex v = g.vertices().iterator().next(); // collect the set of visitable vertices Set visited = GraphAlgorithms.visitDepthFirst( g, v); // throw exception if not all vertices were visited if (visited.size() != c.size()) { throw new IllegalArgumentException( "graph is not connected"); } } /// groups of subject modules with no overlapping students /// /// @param g Graph to inspect /// @return list of non-overlapping sets public static ArrayList> nonOverlapping(Graph g) { // get all subject modules // // TODO: optionally seed this, to enable ranking control List modules = new ArrayList<>(g.vertices()); Collections.shuffle(modules); return nonOverlapping(g, modules); } /// groups of subject modules with no overlapping students /// /// @param g Graph to inspect /// @param vip Ordered list of subject modules to prioritize /// @return list of non-overlapping sets public static ArrayList> nonOverlapping(Graph g, List vip) { ArrayList> sets = new ArrayList<>(); // track done subject modules as extendable set // // HashList used for efficient addition and lookup Set done = new HashSet<>(); // convert module list to a set // // defined once outside loop as a slight optimization Set all_set = new HashSet<>(vip); // iterate over subject modules for (Vertex current : vip) { if (done.contains(current)) { continue; } // add set of current and any unconnected modules Set isolated = all_set.stream().filter(x -> { if (x == current) return false; Integer weight = g.getWeight(current, x); return weight == null && !done.contains(x); }).collect(Collectors.toSet()); // add as set and omit from future iterations sets.add(isolated); done.addAll(isolated); } for(Set s: sets) { System.out.println("set: "+s.size()); for (Vertex v: s) { System.out.println(" "+v.toString()); } } return sets; } }