From 7c146ce8ad65b0b093410ad4a3a3536273ed15b9 Mon Sep 17 00:00:00 2001 From: Luca Conte Date: Mon, 9 Jun 2025 17:01:56 +0200 Subject: [PATCH] first concurrent solution --- projekt/Solver.java | 177 ++++++++++++++++++++++++++++++++------------ 1 file changed, 131 insertions(+), 46 deletions(-) diff --git a/projekt/Solver.java b/projekt/Solver.java index 8cff5ea..30edf0b 100644 --- a/projekt/Solver.java +++ b/projekt/Solver.java @@ -26,10 +26,10 @@ final public class Solver extends JPanel { private static final long serialVersionUID = 1L; // The default size of the labyrinth (i.e. unless program is invoked with size arguments): - private static final int DEFAULT_WIDTH_IN_CELLS = 100; - private static final int DEFAULT_HEIGHT_IN_CELLS = 100; + private static final int DEFAULT_WIDTH_IN_CELLS = 1000; + private static final int DEFAULT_HEIGHT_IN_CELLS = 1000; - private static final int N_RUNS_HALF = 5; // #runs will be 2*N_RUNS_HALF + 1 + private static final int N_RUNS_HALF = 10; // #runs will be 2*N_RUNS_HALF + 1 // The grid defining the structure of the labyrinth private final Labyrinth labyrinth; @@ -38,7 +38,8 @@ final public class Solver extends JPanel { private boolean[][] visited; // initialized in solve() private AtomicBoolean[][] visitedAtomic; - private static final int DISTANCE_PER_TASK = 20; + // determined by trial and error + private static final int DISTANCE_PER_TASK = DEFAULT_WIDTH_IN_CELLS * DEFAULT_HEIGHT_IN_CELLS / 128; private Point[] solution = null; // set to solution path once that has been computed @@ -124,16 +125,19 @@ final public class Solver extends JPanel { public Point[] solveConcurrently() { // dummy origin direction for start - PointAndDirection start = new PointAndDirection(labyrinth.getStart(), Direction.N); + PointAndDirection start = new PointAndDirection(labyrinth.getStart(), null); visitedAtomic = new AtomicBoolean[labyrinth.getWidth()][labyrinth.getHeight()]; - - ArrayDeque backtrackStack = new ArrayDeque(); + for (int i = 0; i < visitedAtomic.length; i++) { + for (int j = 0; j < visitedAtomic[i].length; j++) { + visitedAtomic[i][j] = new AtomicBoolean(false); + } + } ForkJoinPool pool = ForkJoinPool.commonPool(); - - ConcurrentSolverTask t = new ConcurrentSolverTask(backtrackStack, start); + ArrayDeque pathSoFar = new ArrayDeque<>(); + ConcurrentSolverTask t = new ConcurrentSolverTask(start, pathSoFar, pool); Point[] result = pool.invoke(t); return result; @@ -142,56 +146,137 @@ final public class Solver extends JPanel { private class ConcurrentSolverTask extends RecursiveTask { private ArrayDeque backtrackStack; private PointAndDirection start; - private int initialStackSize; private HashSet visitedThisTask; - public ConcurrentSolverTask(ArrayDeque backtrackStack, PointAndDirection start) { - this.backtrackStack = backtrackStack; + private ArrayDeque pathSoFar; + private ForkJoinPool pool; + private ArrayList subtasks; + public ConcurrentSolverTask(PointAndDirection start, ArrayDeque pathSoFar, ForkJoinPool pool) { this.start = start; - this.initialStackSize = backtrackStack.size(); - this.visitedThisTask = new HashSet<>(); + this.pathSoFar = pathSoFar; + this.pool = pool; + + this.backtrackStack = new ArrayDeque<>(); + this.subtasks = new ArrayList<>(); } public Point[] compute() { - PointAndDirection current = this.start; + int currentLength = 0; + Point current = this.start.getPoint(); - if (labyrinth.isDestination(current.getPoint())) { - return backtrackStackToPath(backtrackStack); - } + Point next; + Direction[] dirs = Direction.values(); + + while (!labyrinth.isDestination(current) && !Thread.interrupted()) { + // System.out.println("Visiting " + current); + + next = null; + + for (Direction dir : dirs) { + + // don't check direction of origin + if ( + current.equals(this.start.getPoint()) + && dir.equals(start.getDirectionToBranchingPoint()) + ) { + continue; + } + + // check if labyrinth has passage in this direction + if (!labyrinth.hasPassage(current, dir)) { + continue; + } + + Point neighbour = current.getNeighbor(dir); + + // avoid blind alleys + if ( + labyrinth.isBlindAlley(neighbour, dir.opposite) + && !labyrinth.isDestination(neighbour) + ) { + continue; + } + + // check if that cell has been visited + // set visitedAtomic to true if it has not been visited + if (!visitedAtomic[neighbour.x][neighbour.y].compareAndSet(false, true)) { + continue; + } + // System.out.println("Found unvisited neighbour: " + neighbour); + if (currentLength >= DISTANCE_PER_TASK) { + // if we have reached the distance limit, create a subtask + ArrayDeque subPath = this.pathSoFar.clone(); + subPath.addLast(current); + ConcurrentSolverTask subtask = new ConcurrentSolverTask( + new PointAndDirection(neighbour, dir.opposite), + subPath, + this.pool + ); + subtask.fork(); + subtasks.add(subtask); + } else { + if (next == null) { + // visit first unvisited neighbour next + next = neighbour; + // System.out.println("Visiting next."); + } else { + // push all other unvisited neighbours on backtrack stack to be checked later + this.backtrackStack.push(new PointAndDirection(neighbour, dir.opposite)); + // System.out.println("Pushing to stack."); + } + } + } + + // at least one unvisited neighbour found + if (next != null) { + pathSoFar.addLast(current); + current = next; + currentLength++; + } + if (next == null) { + // no unvisited neighbour found + // backtrack if possible + if (this.backtrackStack.isEmpty()) { + // no solution found from this point + + // check for solutions in subtasks + // System.out.println("End of task reached - waiting for " + subtasks.size() + " subtasks"); + for (ConcurrentSolverTask subtask : subtasks) { + Point[] subtaskResult = subtask.join(); + if (subtaskResult != null) { + // found a solution in a subtask + return subtaskResult; + } + } + // no solution found + return null; + } + + PointAndDirection pd = this.backtrackStack.pop(); + current = pd.getPoint(); + + // Remove the dead end from the top of pathSoFar, i.e. all cells after branchingPoint: + Point branchingPoint = current.getNeighbor(pd.getDirectionToBranchingPoint()); + while (!pathSoFar.isEmpty() && !pathSoFar.peekLast().equals(branchingPoint)) { + pathSoFar.removeLast(); + currentLength--; + } - if (!current.getDirectionToBranchingPoint().equals(Direction.N)) { - if () { - } } - - return null; - } - - private boolean visit(PointAndDirection p) { - if (visitedThisTask.contains(p.getPoint())) { - return false; + // parent thread has found solution or other interrupt + if (Thread.interrupted()) { + return null; } - Point lastPoint = backtrackStack.getLast().getPoint(); - if (Math.abs(lastPoint.y - p.getPoint().y) + Math.abs(lastPoint.x - p.getPoint().x) > 1) { - System.err.println("Cannot visit point " + p.getPoint() + " because it is not adjacened to " + lastPoint); - return false; + pathSoFar.addLast(current); + + // solution found, interrupt subtasks + for (ConcurrentSolverTask subtask : subtasks) { + subtask.cancel(true); } - if (!visitedAtomic[p.getPoint().x][p.getPoint().y].compareAndSet(false, true)) return false; - - backtrackStack.add(p); - visitedThisTask.add(p.getPoint()); - return true; + return pathSoFar.toArray(new Point[0]); } } - - private Point[] backtrackStackToPath(ArrayDeque backtrackStack) { - Point[] result = new Point[backtrackStack.size()]; - for (int i = 0; i < result.length; i++) { - result[backtrackStack.size() - 1] = backtrackStack.remove().getPoint(); - } - return result; - } @Override protected void paintComponent(Graphics graphics) { @@ -321,7 +406,7 @@ private static void displayLabyrinth(Solver solver) { } long startTime = System.currentTimeMillis(); - solver.solution = solver.solve(); + solver.solution = solver.solveConcurrently(); long endTime = System.currentTimeMillis(); if (solver.solution == null)