Compare commits

..

No commits in common. "e1c93881afa5489cbb0d67255dcbec98826fcef1" and "4cfaf3803c6005e36249d544a2a131cdce8f6446" have entirely different histories.

35 changed files with 54 additions and 381 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
/ki_rwu_java.iml
/.idea/
/lib/
/out/

View File

@ -1,3 +0,0 @@
# KI_RWU
This is a repository containing basic AI algorithms taught in the AI lecture at _Hochschule Ravensburg-Weingarten_.

0
resources/app1.data Executable file → Normal file
View File

0
resources/app1.test Executable file → Normal file
View File

0
src/machine_learning/DataClass.java Executable file → Normal file
View File

0
src/machine_learning/MachineLearning.java Executable file → Normal file
View File

23
src/machine_learning/Vector.java Executable file → Normal file
View File

@ -34,7 +34,7 @@ public class Vector
public Vector add(Vector b) public Vector add(Vector b)
{ {
checkEqualDimensions(b); if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals.");
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)
{ {
checkEqualDimensions(b); if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals.");
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)
{ {
checkEqualDimensions(b); if (this.dimension() != b.dimension()) throw new IllegalArgumentException("Dimensions must be equals.");
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,9 +80,14 @@ public class Vector
public Vector divide(double div) public Vector divide(double div)
{ {
return new Vector(IntStream.range(0, this.dimension()) var divided = new ArrayList<Double>();
.mapToObj(i -> this.values.get(i) / div)
.collect(Collectors.toCollection(ArrayList::new))); for (int i = 0; i < this.dimension(); i++)
{
divided.add(this.values.get(i) / div);
}
return new Vector(divided);
} }
public double get(int index) public double get(int index)
@ -128,10 +133,4 @@ 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

View File

@ -2,7 +2,7 @@ package machine_learning.nearest_neighbour;
import machine_learning.Vector; import machine_learning.Vector;
public interface DistanceFunction public interface Distance
{ {
double distance(Vector a, Vector b); double distance(Vector a, Vector b);
} }

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 DistanceFunction distanceFunction; private final Distance distance;
private final int k; private final int k;
public KNearestNeighbour(DistanceFunction distanceFunction) public KNearestNeighbour(Distance distance)
{ {
this(distanceFunction, 1); this(distance, 1);
} }
public KNearestNeighbour(DistanceFunction distanceFunction, int k) public KNearestNeighbour(Distance distance, int k)
{ {
this.distanceFunction = distanceFunction; this.distance = distance;
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.distanceFunction.distance(v, vector), v)) .map(v -> Map.entry(this.distance.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())

0
src/machine_learning/perceptron/Perceptron.java Executable file → Normal file
View File

View File

@ -1,93 +0,0 @@
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);
}
}
}

0
src/search/Direction.java Executable file → Normal file
View File

0
src/search/EightPuzzleNode.java Executable file → Normal file
View File

0
src/search/IntPair.java Executable file → Normal file
View File

0
src/search/LabyrinthineNode.java Executable file → Normal file
View File

0
src/search/Node.java Executable file → Normal file
View File

0
src/search/heuristic/AStar.java Executable file → Normal file
View File

0
src/search/heuristic/HeuristicEstimationFunction.java Executable file → Normal file
View File

View File

View File

View File

0
test/machine_learning/VectorTest.java Executable file → Normal file
View File

View File

@ -3,7 +3,6 @@ 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;
@ -23,9 +22,8 @@ import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KNearestNeighbourTest class KNearestNeighbourTest
{ {
private List<Vector> positives; List<Vector> positives;
private List<Vector> negatives; List<Vector> negatives;
private DistanceFunction distanceFunction;
@BeforeAll @BeforeAll
void initLearnData() void initLearnData()
@ -43,14 +41,12 @@ 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(this.distanceFunction, 3); var kNearestNeighbour = new KNearestNeighbour((a ,b) -> Math.abs(a.get(0) - b.get(0)) + Math.abs(a.get(1) - b.get(1)), 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);
@ -63,7 +59,7 @@ class KNearestNeighbourTest
@Test @Test
public void shouldReturnCorrectClassForVectorWithKEquals5() public void shouldReturnCorrectClassForVectorWithKEquals5()
{ {
var kNearestNeighbour = new KNearestNeighbour(this.distanceFunction, 5); var kNearestNeighbour = new KNearestNeighbour((a ,b) -> Math.abs(a.get(0) - b.get(0)) + Math.abs(a.get(1) - b.get(1)), 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);
@ -114,7 +110,6 @@ class KNearestNeighbourTest
} }
@Disabled("Takes long")
@Test @Test
public void shouldReturnOptimum() public void shouldReturnOptimum()
{ {

12
test/machine_learning/perceptron/PerceptronTest.java Executable file → Normal file
View File

@ -1,12 +1,16 @@
package machine_learning.perceptron; package machine_learning.perceptron;
import machine_learning.DataClass;
import machine_learning.Vector; import machine_learning.Vector;
import org.junit.jupiter.api.*; import machine_learning.DataClass;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
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
{ {
@ -44,7 +48,7 @@ class PerceptronTest
var actualClass = this.perceptron.classify(vector); var actualClass = this.perceptron.classify(vector);
var expectedClass = DataClass.NEGATIVE; var expectedClass = DataClass.NEGATIVE;
Assertions.assertEquals(expectedClass, actualClass); assertEquals(expectedClass, actualClass);
} }
@Test @Test
@ -55,6 +59,6 @@ class PerceptronTest
var actualClass = this.perceptron.classify(vector); var actualClass = this.perceptron.classify(vector);
var expectedClass = DataClass.POSITIVE; var expectedClass = DataClass.POSITIVE;
Assertions.assertEquals(expectedClass, actualClass); assertEquals(expectedClass, actualClass);
} }
} }

View File

@ -1,107 +0,0 @@
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());
}
}

View File

@ -1,51 +0,0 @@
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);
}
}

0
test/search/EightPuzzleNodeTest.java Executable file → Normal file
View File

View File

@ -1,77 +0,0 @@
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);
}
}

0
test/search/LabyrinthineNodeTest.java Executable file → Normal file
View File

14
test/search/SearchTestUtils.java Executable file → Normal file
View File

@ -17,18 +17,4 @@ 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;
}
} }

0
test/search/heuristic/AStarTest.java Executable file → Normal file
View File

View File

View File

View File

@ -51,4 +51,28 @@ 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);
}
} }