Compare commits

...
10 Commits
35 changed files with 381 additions and 54 deletions

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
/ki_rwu_java.iml
/.idea/
/lib/
/out/
+3
View File
@@ -0,0 +1,3 @@
# KI_RWU
This is a repository containing basic AI algorithms taught in the AI lecture at _Hochschule Ravensburg-Weingarten_.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
Regular → Executable
+12 -11
View File
@@ -34,7 +34,7 @@ public class Vector
public Vector add(Vector b) public Vector add(Vector b)
{ {
if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals."); checkEqualDimensions(b);
return new Vector(IntStream.range(0, return new Vector(IntStream.range(0,
this.dimension()) this.dimension())
.mapToObj(i -> this.get(i) + b.get(i)) .mapToObj(i -> this.get(i) + b.get(i))
@@ -45,7 +45,7 @@ public class Vector
public Vector subtract(Vector b) public Vector subtract(Vector b)
{ {
if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals."); checkEqualDimensions(b);
return new Vector(IntStream.range(0, return new Vector(IntStream.range(0,
this.dimension()) this.dimension())
.mapToObj(i -> this.get(i) - b.get(i)) .mapToObj(i -> this.get(i) - b.get(i))
@@ -55,7 +55,7 @@ public class Vector
public double scalar(Vector b) public double scalar(Vector b)
{ {
if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals."); checkEqualDimensions(b);
return IntStream.range(0, return IntStream.range(0,
this.dimension()) this.dimension())
.mapToDouble(i -> this.get(i) * b.get(i)) .mapToDouble(i -> this.get(i) * b.get(i))
@@ -80,14 +80,9 @@ public class Vector
public Vector divide(double div) public Vector divide(double div)
{ {
var divided = new ArrayList<Double>(); return new Vector(IntStream.range(0, this.dimension())
.mapToObj(i -> this.values.get(i) / div)
for (int i = 0; i < this.dimension(); i++) .collect(Collectors.toCollection(ArrayList::new)));
{
divided.add(this.values.get(i) / div);
}
return new Vector(divided);
} }
public double get(int index) public double get(int index)
@@ -133,4 +128,10 @@ public class Vector
{ {
return this.values.toString(); return this.values.toString();
} }
private void checkEqualDimensions(Vector b)
{
if (this.dimension() != b.dimension())
throw new IllegalArgumentException("Dimensions must be equal");
}
} }
View File
File mode changed.
@@ -2,7 +2,7 @@ package machine_learning.nearest_neighbour;
import machine_learning.Vector; import machine_learning.Vector;
public interface Distance public interface DistanceFunction
{ {
double distance(Vector a, Vector b); double distance(Vector a, Vector b);
} }
+6 -6
View File
@@ -15,18 +15,18 @@ public class KNearestNeighbour implements MachineLearning
private List<Vector> positives; private List<Vector> positives;
private List<Vector> negatives; private List<Vector> negatives;
private final Distance distance; private final DistanceFunction distanceFunction;
private final int k; private final int k;
public KNearestNeighbour(Distance distance) public KNearestNeighbour(DistanceFunction distanceFunction)
{ {
this(distance, 1); this(distanceFunction, 1);
} }
public KNearestNeighbour(Distance distance, int k) public KNearestNeighbour(DistanceFunction distanceFunction, int k)
{ {
this.distance = distance; this.distanceFunction = distanceFunction;
this.k = k; this.k = k;
} }
@@ -67,7 +67,7 @@ public class KNearestNeighbour implements MachineLearning
private List<Vector> nearestNeighbours(List<Vector> vectors, Vector vector) private List<Vector> nearestNeighbours(List<Vector> vectors, Vector vector)
{ {
return vectors.parallelStream() return vectors.parallelStream()
.map(v -> Map.entry(this.distance.distance(v, vector), v)) .map(v -> Map.entry(this.distanceFunction.distance(v, vector), v))
.sorted((e1, e2) -> e1.getKey() >= e2.getKey() ? (e1.getKey().equals(e2.getKey()) ? 0 : 1) : -1) .sorted((e1, e2) -> e1.getKey() >= e2.getKey() ? (e1.getKey().equals(e2.getKey()) ? 0 : 1) : -1)
.map(Map.Entry::getValue) .map(Map.Entry::getValue)
.collect(Collectors.toList()) .collect(Collectors.toList())
View File
File mode changed.
+93
View File
@@ -0,0 +1,93 @@
package search;
import java.util.ArrayList;
import java.util.List;
/**
* For a Kata on codewars.com.
* How many moves need a knight (Springer) to get from a given start to a given end on the chessboard,
* e.g. a3 -> b5, the knight needs 1 move;
* a1 -> f1, the knight needs 3 moves
*/
public class ChessKnightNode extends Node<ChessKnightNode.KnightPair> {
public ChessKnightNode(final KnightPair state) {
super(state);
}
private ChessKnightNode(final KnightPair value, final Node<KnightPair> parent, final int heuristicCosts) {
super(value, parent, heuristicCosts);
}
@Override
public boolean isTargetReached(final Node<KnightPair> target) {
return this.valueEquals(target);
}
@Override
public List<Node<KnightPair>> generateSuccessors() {
final var successors = new ArrayList<Node<KnightPair>>();
for (final var dir : ChessDirections.values()) {
try {
final char newCol = (char) (super.value.col() + dir.getColChange());
final char newRow = (char) (super.value.row() + dir.getRowChange());
final var newState = new KnightPair(newCol, newRow);
final var successor = new ChessKnightNode(newState, this, super.heuristicCosts + 1);
if (!successor.valueEquals(this) && !successor.valueEquals(super.getParent())) {
successors.add(successor);
}
} catch (final ChessKnightException ignored) {
}
}
return successors;
}
private boolean valueEquals(final Node<KnightPair> node) {
return node != null && super.value.equals(node.value);
}
@Override
public String toString() {
return "%c%c".formatted(super.value.col, super.value.row);
}
protected record KnightPair(char col, char row) {
public KnightPair {
if (col < 'a' || col > 'h') throw new ChessKnightException("Row out of bounds at index %c".formatted(col));
if (row < '1' || row > '8') throw new ChessKnightException("Column out of bounds at index %c".formatted(row));
}
}
protected enum ChessDirections {
TOP_LEFT(-1, 2), TOP_RIGHT(1, 2),
RIGHT_TOP(2, 1), RIGHT_BOTTOM(2, -1),
DOWN_RIGHT(1 ,-2), DOWN_LEFT(-1,-2),
LEFT_BOTTOM(-2,-1), LEFT_TOP(-2,1);
public final int colChange;
public final int rowChange;
ChessDirections( final int colChange, final int rowChange) {
this.colChange = colChange;
this.rowChange = rowChange;
}
public int getColChange() {
return colChange;
}
public int getRowChange() {
return rowChange;
}
}
protected static class ChessKnightException extends IndexOutOfBoundsException {
public ChessKnightException(final String s) {
super(s);
}
}
}
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
Regular → Executable
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
+9 -4
View File
@@ -3,6 +3,7 @@ package machine_learning.nearest_neighbour;
import machine_learning.DataClass; import machine_learning.DataClass;
import machine_learning.Vector; import machine_learning.Vector;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.opentest4j.AssertionFailedError; import org.opentest4j.AssertionFailedError;
@@ -22,8 +23,9 @@ import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KNearestNeighbourTest class KNearestNeighbourTest
{ {
List<Vector> positives; private List<Vector> positives;
List<Vector> negatives; private List<Vector> negatives;
private DistanceFunction distanceFunction;
@BeforeAll @BeforeAll
void initLearnData() void initLearnData()
@@ -41,12 +43,14 @@ class KNearestNeighbourTest
new Vector(8d, 2d), new Vector(8d, 2d),
new Vector(9d, 0d)) new Vector(9d, 0d))
); );
this.distanceFunction = (a, b) -> Math.abs(a.get(0) - b.get(0)) + Math.abs(a.get(1) - b.get(1));
} }
@Test @Test
public void shouldReturnCorrectClassForVectorWithKEquals3() public void shouldReturnCorrectClassForVectorWithKEquals3()
{ {
var kNearestNeighbour = new KNearestNeighbour((a ,b) -> Math.abs(a.get(0) - b.get(0)) + Math.abs(a.get(1) - b.get(1)), 3); var kNearestNeighbour = new KNearestNeighbour(this.distanceFunction, 3);
kNearestNeighbour.learn(this.positives, this.negatives); kNearestNeighbour.learn(this.positives, this.negatives);
var vector = new Vector(8, 3.5); var vector = new Vector(8, 3.5);
@@ -59,7 +63,7 @@ class KNearestNeighbourTest
@Test @Test
public void shouldReturnCorrectClassForVectorWithKEquals5() public void shouldReturnCorrectClassForVectorWithKEquals5()
{ {
var kNearestNeighbour = new KNearestNeighbour((a ,b) -> Math.abs(a.get(0) - b.get(0)) + Math.abs(a.get(1) - b.get(1)), 5); var kNearestNeighbour = new KNearestNeighbour(this.distanceFunction, 5);
kNearestNeighbour.learn(this.positives, this.negatives); kNearestNeighbour.learn(this.positives, this.negatives);
var vector = new Vector(8, 3.5); var vector = new Vector(8, 3.5);
@@ -110,6 +114,7 @@ class KNearestNeighbourTest
} }
@Disabled("Takes long")
@Test @Test
public void shouldReturnOptimum() public void shouldReturnOptimum()
{ {
+4 -8
View File
@@ -1,16 +1,12 @@
package machine_learning.perceptron; package machine_learning.perceptron;
import machine_learning.Vector;
import machine_learning.DataClass; import machine_learning.DataClass;
import org.junit.jupiter.api.BeforeAll; import machine_learning.Vector;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.TestInstance;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PerceptronTest class PerceptronTest
{ {
@@ -48,7 +44,7 @@ class PerceptronTest
var actualClass = this.perceptron.classify(vector); var actualClass = this.perceptron.classify(vector);
var expectedClass = DataClass.NEGATIVE; var expectedClass = DataClass.NEGATIVE;
assertEquals(expectedClass, actualClass); Assertions.assertEquals(expectedClass, actualClass);
} }
@Test @Test
@@ -59,6 +55,6 @@ class PerceptronTest
var actualClass = this.perceptron.classify(vector); var actualClass = this.perceptron.classify(vector);
var expectedClass = DataClass.POSITIVE; var expectedClass = DataClass.POSITIVE;
assertEquals(expectedClass, actualClass); Assertions.assertEquals(expectedClass, actualClass);
} }
} }
+107
View File
@@ -0,0 +1,107 @@
package search;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class ChessKnightNodeTest
{
@Test
public void shouldReturnTrueWhenTargetReached()
{
final var state = new ChessKnightNode.KnightPair('a', '1');
final var node = new ChessKnightNode(state);
final var target = new ChessKnightNode(state);
Assertions.assertTrue(node.isTargetReached(target));
}
@Test
public void shouldReturnFalseWhenTargetNotReached()
{
final var actualState = new ChessKnightNode.KnightPair('a', '1');
final var targetState = new ChessKnightNode.KnightPair('h', '8');
final var node = new ChessKnightNode(actualState);
final var target = new ChessKnightNode(targetState);
Assertions.assertFalse(node.isTargetReached(target));
}
@Test
public void shouldReturnNonEmptyListOfSuccessors()
{
final var state = new ChessKnightNode.KnightPair('a', '1');
final var node = new ChessKnightNode(state);
final var successors = node.generateSuccessors();
Assertions.assertFalse(successors.isEmpty());
}
@Test
public void shouldReturnCorrectSuccessorsWithMaxPossibleSuccessors()
{
final var state = new ChessKnightNode.KnightPair('f', '3');
final var node = new ChessKnightNode(state);
final var successors = node.generateSuccessors();
Assertions.assertEquals(8, successors.size());
final var newState1 = new ChessKnightNode.KnightPair('e', '5');
final var newState2 = new ChessKnightNode.KnightPair('g', '5');
final var newState3 = new ChessKnightNode.KnightPair('h', '4');
final var newState4 = new ChessKnightNode.KnightPair('h', '2');
final var newState5 = new ChessKnightNode.KnightPair('g', '1');
final var newState6 = new ChessKnightNode.KnightPair('e', '1');
final var newState7 = new ChessKnightNode.KnightPair('d', '2');
final var newState8 = new ChessKnightNode.KnightPair('d', '4');
Assertions.assertEquals(newState1, successors.get(0).getValue());
Assertions.assertEquals(newState2, successors.get(1).getValue());
Assertions.assertEquals(newState3, successors.get(2).getValue());
Assertions.assertEquals(newState4, successors.get(3).getValue());
Assertions.assertEquals(newState5, successors.get(4).getValue());
Assertions.assertEquals(newState6, successors.get(5).getValue());
Assertions.assertEquals(newState7, successors.get(6).getValue());
Assertions.assertEquals(newState8, successors.get(7).getValue());
}
@Test
public void shouldReturnCorrectSuccessorsOnEdge()
{
final var state = new ChessKnightNode.KnightPair('a', '1');
final var node = new ChessKnightNode(state);
final var successors = node.generateSuccessors();
Assertions.assertEquals(2, successors.size());
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair((char) ('a' - 1), '3'));
final var newState1 = new ChessKnightNode.KnightPair('b', '3');
final var newState2 = new ChessKnightNode.KnightPair('c', '2');
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair('c', '0'));
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair('c', (char) ('0' - 1)));
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair((char) ('a' - 1), (char) ('0' - 1)));
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair((char) ('a' - 2), '0'));
Assertions.assertThrows(ChessKnightNode.ChessKnightException.class,
() -> new ChessKnightNode.KnightPair((char) ('a' - 2), '3'));
Assertions.assertEquals(newState1, successors.get(0).getValue());
Assertions.assertEquals(newState2, successors.get(1).getValue());
}
}
+51
View File
@@ -0,0 +1,51 @@
package search;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import search.uninformed.breadthfirstsearch.BreadthFirstSearch;
import java.util.List;
import static search.SearchTestUtils.countNodes;
import static search.SearchTestUtils.printSolution;
public class ChessKnightTest
{
@Test
public void shouldReturnOne()
{
final var state = new ChessKnightNode.KnightPair('a', '3');
final var root = new ChessKnightNode(state);
final var targetState = new ChessKnightNode.KnightPair('b', '5');
final var expected = new ChessKnightNode(targetState);
final var actual = new BreadthFirstSearch().breadthFirstSearch(List.of(root), expected);
printSolution(actual);
var nodeCount = countNodes(actual);
var edgeCount = nodeCount - 1;
Assertions.assertEquals(1, edgeCount);
}
@Test
public void shouldReturnThree()
{
final var state = new ChessKnightNode.KnightPair('a', '1');
final var root = new ChessKnightNode(state);
final var targetState = new ChessKnightNode.KnightPair('f', '1');
final var expected = new ChessKnightNode(targetState);
final var actual = new BreadthFirstSearch().breadthFirstSearch(List.of(root), expected);
printSolution(actual);
var nodeCount = countNodes(actual);
var edgeCount = nodeCount - 1;
Assertions.assertEquals(3, edgeCount);
}
}
View File
File mode changed.
+77
View File
@@ -0,0 +1,77 @@
package search;
import org.junit.jupiter.api.Test;
import search.uninformed.iterativedeepening.IterativeDeepening;
import static search.SearchTestUtils.printSolution;
public class GameHelperTest
{
@Test
void shouldReturnCorrectTargetDemonologist()
{
final int[][] state = {
{8, 4 ,0},
{6, 3, 1},
{2, 5, 7}
};
final var root = new EightPuzzleNode(state);
final int[][] targetState = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 0}
};
final var expected = new EightPuzzleNode(targetState);
final var actual = new IterativeDeepening().iterativeDeepening(root, expected);
printSolution(actual);
}
@Test
void shouldReturnCorrectTargetWartales()
{
final int[][] state = {
{1, 8, 6},
{3, 2, 7},
{5, 4, 0}
};
final var root = new EightPuzzleNode(state);
final int[][] targetState = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 0}
};
final var expected = new EightPuzzleNode(targetState);
final var actual = new IterativeDeepening().iterativeDeepening(root, expected);
printSolution(actual);
}
@Test
void shouldReturnCorrectTargetLabyrinthine()
{
final boolean[][] state = {
{true, false, true},
{false, true, false},
{true, false, true}
};
final var root = new LabyrinthineNode(state);
final boolean[][] targetState = {
{true, true, true},
{true, true, true},
{true, true, true},
};
final var expected = new LabyrinthineNode(targetState);
final var actual = new IterativeDeepening().iterativeDeepening(root, expected);
printSolution(actual);
}
}
View File
File mode changed.
Regular → Executable
+14
View File
@@ -17,4 +17,18 @@ public class SearchTestUtils
System.out.println("START"); System.out.println("START");
} }
public static <T> int countNodes(final Node<T> targetNode)
{
var node = targetNode;
int nodeCount = 0;
while (node != null)
{
nodeCount++;
node = node.getParent();
}
return nodeCount;
}
} }
View File
File mode changed.
View File
File mode changed.
View File
File mode changed.
-24
View File
@@ -51,28 +51,4 @@ class IterativeDeepeningTest
printSolution(actual); printSolution(actual);
} }
@Test
void shouldReturnCorrectTargetLabyrinthine()
{
final boolean[][] state = {
{true, false, true},
{false, true, false},
{true, false, true}
};
final var root = new LabyrinthineNode(state);
final boolean[][] targetState = {
{true, true, true},
{true, true, true},
{true, true, true},
};
final var expected = new LabyrinthineNode(targetState);
final var actual = new IterativeDeepening().iterativeDeepening(root, expected);
printSolution(actual);
}
} }