From 82e7e618d75e293cf79e9af098680eaa9a68c37d Mon Sep 17 00:00:00 2001 From: Niklas Birk Date: Wed, 27 Mar 2019 19:59:28 +0100 Subject: [PATCH] Changed several implementation details and added depth-first-search --- .idea/inspectionProfiles/Project_Default.xml | 8 +- src/{search/breadthfirstsearch => }/Main.java | 2 - .../EightPuzzleNode.java | 132 ++++++++++-------- src/search/{breadthfirstsearch => }/Node.java | 12 +- .../BreadthFirstSearch.java | 2 + .../depthfirstsearch/DepthFirstSearch.java | 33 +++++ .../EightPuzzleNodeTest.java | 3 +- test/search/SearchTestUtils.java | 20 +++ .../BreadthFirstSearchTest.java | 26 +++- .../DepthFirstSearchTest.java | 56 ++++++++ 10 files changed, 216 insertions(+), 78 deletions(-) rename src/{search/breadthfirstsearch => }/Main.java (68%) rename src/search/{breadthfirstsearch => }/EightPuzzleNode.java (59%) rename src/search/{breadthfirstsearch => }/Node.java (74%) create mode 100644 src/search/depthfirstsearch/DepthFirstSearch.java rename test/search/{breadthfirstsearch => }/EightPuzzleNodeTest.java (98%) create mode 100644 test/search/SearchTestUtils.java create mode 100644 test/search/depthfirstsearch/DepthFirstSearchTest.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 0b9b0fb..25c2334 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,12 @@ \ No newline at end of file diff --git a/src/search/breadthfirstsearch/Main.java b/src/Main.java similarity index 68% rename from src/search/breadthfirstsearch/Main.java rename to src/Main.java index f74021c..e90a4ee 100644 --- a/src/search/breadthfirstsearch/Main.java +++ b/src/Main.java @@ -1,5 +1,3 @@ -package search.breadthfirstsearch; - public class Main { public static void main(String[] args) diff --git a/src/search/breadthfirstsearch/EightPuzzleNode.java b/src/search/EightPuzzleNode.java similarity index 59% rename from src/search/breadthfirstsearch/EightPuzzleNode.java rename to src/search/EightPuzzleNode.java index 1b8fd07..e2ad6c1 100644 --- a/src/search/breadthfirstsearch/EightPuzzleNode.java +++ b/src/search/EightPuzzleNode.java @@ -1,4 +1,4 @@ -package search.breadthfirstsearch; +package search; import java.util.ArrayList; import java.util.Arrays; @@ -21,18 +21,7 @@ public class EightPuzzleNode extends Node @Override public boolean isTargetReached(Node target) { - for (int row = 0; row < super.value.length; row++) - { - for (int col = 0; col < super.value[row].length; col++) - { - if (super.value[row][col] != target.value[row][col]) - { - return false; - } - } - } - - return true; + return valueEquals(target); } @Override @@ -40,8 +29,8 @@ public class EightPuzzleNode extends Node { var successors = new ArrayList>(); var emptyPosition = Objects.requireNonNull(detectEmptyPosition()); - int x = emptyPosition.getRight(); - int y = emptyPosition.getLeft(); + int x = emptyPosition.getX(); + int y = emptyPosition.getY(); for (Direction dir : Direction.values()) { @@ -49,34 +38,20 @@ public class EightPuzzleNode extends Node try { - int tmp; - switch (dir) + var posToSwap = switch (dir) { + case TOP -> new IntPair(x, y-1); + case RIGHT -> new IntPair(x+1, y); + case DOWN -> new IntPair(x, y+1); + case LEFT -> new IntPair(x-1, y); + }; + + var successor = this.swapStateField(newState, emptyPosition, posToSwap); + + if (!successor.valueEquals(this) && !successor.valueEquals(super.getParent())) { - case TOP: - tmp = newState[y-1][x]; - newState[y-1][x] = newState[y][x]; - newState[y][x] = tmp; - successors.add(new EightPuzzleNode(newState, this)); - break; - case RIGHT: - tmp = newState[y][x+1]; - newState[y][x+1] = newState[y][x]; - newState[y][x] = tmp; - successors.add(new EightPuzzleNode(newState, this)); - break; - case DOWN: - tmp = newState[y+1][x]; - newState[y+1][x] = newState[y][x]; - newState[y][x] = tmp; - successors.add(new EightPuzzleNode(newState, this)); - break; - case LEFT: - tmp = newState[y][x-1]; - newState[y][x-1] = newState[y][x]; - newState[y][x] = tmp; - successors.add(new EightPuzzleNode(newState, this)); - break; + successors.add(successor); } + } catch (ArrayIndexOutOfBoundsException ignored) { @@ -87,13 +62,47 @@ public class EightPuzzleNode extends Node } @Override - protected boolean isValidValue(int[][] state) + protected boolean isValidParameterValue(int[][] state) { var numbers = Arrays.stream(state).flatMapToInt(IntStream::of).toArray(); return numbersAreInAllowedRange(numbers) && numbersAreUnique(numbers); } + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + + for (int[] row : super.value) + { + builder.append(Arrays.toString(row)).append("\n"); + } + + return builder.toString(); + } + + private boolean valueEquals(Node node) + { + if (node == null) + { + return false; + } + + for (int row = 0; row < super.value.length; row++) + { + for (int col = 0; col < super.value[row].length; col++) + { + if (super.value[row][col] != node.value[row][col]) + { + return false; + } + } + } + + return true; + } + private boolean numbersAreInAllowedRange(int[] numbers) { return Arrays.stream(numbers).min().getAsInt() == 0 && Arrays.stream(numbers).max().getAsInt() == 8; @@ -112,7 +121,7 @@ public class EightPuzzleNode extends Node { if (super.value[row][col] == 0) { - return new IntPair(row, col); + return new IntPair(col, row); } } } @@ -132,38 +141,39 @@ public class EightPuzzleNode extends Node return copy; } - @Override - public String toString() + private EightPuzzleNode swapStateField(int[][] newState, IntPair emptyPos, IntPair posToSwap) { - StringBuilder builder = new StringBuilder("Node:\n"); + int posToSwapX = posToSwap.getX(); + int postToSwapY = posToSwap.getY(); + int emptyX = emptyPos.getX(); + int emptyY = emptyPos.getY(); + int tmp; - for (int[] row : super.value) - { - builder.append(Arrays.toString(row)).append("\n"); - } + tmp = newState[postToSwapY][posToSwapX]; + newState[postToSwapY][posToSwapX] = newState[emptyY][emptyX]; + newState[emptyY][emptyX] = tmp; - return builder.toString(); + return new EightPuzzleNode(newState, this); } private class IntPair { - private final int left; + private final int x; + private final int y; - private final int right; - - public IntPair(int left, int right) + public IntPair(int x, int y) { - this.left = left; - this.right = right; + this.x = x; + this.y = y; } - public int getLeft() + public int getX() { - return left; + return x; } - public int getRight() + public int getY() { - return right; + return y; } } diff --git a/src/search/breadthfirstsearch/Node.java b/src/search/Node.java similarity index 74% rename from src/search/breadthfirstsearch/Node.java rename to src/search/Node.java index 3cc038e..db163a8 100644 --- a/src/search/breadthfirstsearch/Node.java +++ b/src/search/Node.java @@ -1,4 +1,4 @@ -package search.breadthfirstsearch; +package search; import java.util.List; @@ -14,7 +14,7 @@ public abstract class Node protected Node(T value, Node parent) { - if (!isValidValue(value)) + if (!isValidParameterValue(value)) { throw new IllegalArgumentException("Illegal node value"); } @@ -33,13 +33,7 @@ public abstract class Node return this.parent; } - @Override - public String toString() - { - return this.value.toString(); - } - - protected abstract boolean isValidValue(T value); + protected abstract boolean isValidParameterValue(T value); public abstract boolean isTargetReached(Node target); public abstract List> generateSuccessors(); } diff --git a/src/search/breadthfirstsearch/BreadthFirstSearch.java b/src/search/breadthfirstsearch/BreadthFirstSearch.java index 6438b10..fa8e7a2 100644 --- a/src/search/breadthfirstsearch/BreadthFirstSearch.java +++ b/src/search/breadthfirstsearch/BreadthFirstSearch.java @@ -1,5 +1,7 @@ package search.breadthfirstsearch; +import search.Node; + import java.util.ArrayList; import java.util.List; diff --git a/src/search/depthfirstsearch/DepthFirstSearch.java b/src/search/depthfirstsearch/DepthFirstSearch.java new file mode 100644 index 0000000..110eaae --- /dev/null +++ b/src/search/depthfirstsearch/DepthFirstSearch.java @@ -0,0 +1,33 @@ +package search.depthfirstsearch; + +import search.Node; + +import java.util.ArrayList; +import java.util.List; + +public class DepthFirstSearch +{ + public Node depthFirstSearch(Node node, Node target) + { + if (node.isTargetReached(target)) + { + return node; + } + + var newNodes = node.generateSuccessors(); + + while (!newNodes.isEmpty()) + { + var resultNode = depthFirstSearch(newNodes.get(0), target); + + if (resultNode != null) + { + return resultNode; + } + + newNodes.remove(0); + } + + return null; + } +} diff --git a/test/search/breadthfirstsearch/EightPuzzleNodeTest.java b/test/search/EightPuzzleNodeTest.java similarity index 98% rename from test/search/breadthfirstsearch/EightPuzzleNodeTest.java rename to test/search/EightPuzzleNodeTest.java index 558bbd0..98bd855 100644 --- a/test/search/breadthfirstsearch/EightPuzzleNodeTest.java +++ b/test/search/EightPuzzleNodeTest.java @@ -1,7 +1,8 @@ -package search.breadthfirstsearch; +package search; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import search.EightPuzzleNode; class EightPuzzleNodeTest { diff --git a/test/search/SearchTestUtils.java b/test/search/SearchTestUtils.java new file mode 100644 index 0000000..1bedc34 --- /dev/null +++ b/test/search/SearchTestUtils.java @@ -0,0 +1,20 @@ +package search; + +public class SearchTestUtils +{ + public static void printSolution(Node targetNode) + { + var node = targetNode; + + System.out.println("Read from down to top!"); + System.out.println("END"); + + while (node != null) + { + System.out.println(node); + node = node.getParent(); + } + + System.out.println("START"); + } +} diff --git a/test/search/breadthfirstsearch/BreadthFirstSearchTest.java b/test/search/breadthfirstsearch/BreadthFirstSearchTest.java index ff8f1c9..d51b2b7 100644 --- a/test/search/breadthfirstsearch/BreadthFirstSearchTest.java +++ b/test/search/breadthfirstsearch/BreadthFirstSearchTest.java @@ -1,10 +1,12 @@ package search.breadthfirstsearch; import org.junit.jupiter.api.Test; +import search.EightPuzzleNode; -import java.util.Arrays; import java.util.List; +import static search.SearchTestUtils.printSolution; + class BreadthFirstSearchTest { @Test @@ -26,12 +28,28 @@ class BreadthFirstSearchTest var actual = new BreadthFirstSearch().breadthFirstSearch(List.of(root), expected); - System.out.println("Target: " + Arrays.deepToString(targetState)); - System.out.println("Actual:\n" + actual); + printSolution(actual); } - private void printSolution(Node targetNode) + @Test + void shouldReturnCorrectTargetChubekNode() { + int[][] state = { + {2, 0, 4}, + {6, 7, 1}, + {8, 5, 3} + }; + var root = new EightPuzzleNode(state); + int[][] targetState = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 0} + }; + var expected = new EightPuzzleNode(targetState); + + var actual = new BreadthFirstSearch().breadthFirstSearch(List.of(root), expected); + + printSolution(actual); } } \ No newline at end of file diff --git a/test/search/depthfirstsearch/DepthFirstSearchTest.java b/test/search/depthfirstsearch/DepthFirstSearchTest.java new file mode 100644 index 0000000..e9732cb --- /dev/null +++ b/test/search/depthfirstsearch/DepthFirstSearchTest.java @@ -0,0 +1,56 @@ +package search.depthfirstsearch; + +import org.junit.jupiter.api.Test; +import search.EightPuzzleNode; +import search.breadthfirstsearch.BreadthFirstSearch; + +import java.util.List; + +import static search.SearchTestUtils.printSolution; + +class DepthFirstSearchTest +{ + @Test + void shouldReturnCorrectTarget() + { + int[][] state = { + {1, 2, 3}, + {4, 5, 6}, + {7, 0, 8} + }; + var root = new EightPuzzleNode(state); + + int[][] targetState = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 0} + }; + var expected = new EightPuzzleNode(targetState); + + var actual = new DepthFirstSearch().depthFirstSearch(root, expected); + + printSolution(actual); + } + +// @Test +// void shouldReturnCorrectTargetChubekNode() +// { +// int[][] state = { +// {2, 0, 4}, +// {6, 7, 1}, +// {8, 5, 3} +// }; +// var root = new EightPuzzleNode(state); +// +// int[][] targetState = { +// {1, 2, 3}, +// {4, 5, 6}, +// {7, 8, 0} +// }; +// var expected = new EightPuzzleNode(targetState); +// +// var actual = new DepthFirstSearch().depthFirstSearch(root, expected); +// +// printSolution(actual); +// } +} \ No newline at end of file