From e2ab2539807ab4e965e195bf522cda3dcbf5bfe7 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Thu, 4 Apr 2024 00:29:51 +0200 Subject: [PATCH] Improved examples --- .../com/ledmington/gal/examples/Diet.java | 16 +- .../ledmington/gal/examples/GeneticTsp.java | 271 +++++++++++++----- .../com/ledmington/gal/examples/Knapsack.java | 235 +++++++++------ .../com/ledmington/gal/examples/Main.java | 2 +- .../gal/examples/NeuralNetwork.java | 21 +- .../gal/examples/RandomStrings.java | 129 +++++---- 6 files changed, 450 insertions(+), 224 deletions(-) diff --git a/examples/src/main/java/com/ledmington/gal/examples/Diet.java b/examples/src/main/java/com/ledmington/gal/examples/Diet.java index 089664c..07dd216 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/Diet.java +++ b/examples/src/main/java/com/ledmington/gal/examples/Diet.java @@ -136,6 +136,7 @@ public boolean equals(final Object other) { } public Diet() { + final long beginning = System.nanoTime(); final RandomGenerator rng = RandomGeneratorFactory.getDefault().create(System.nanoTime()); final Supplier> state = @@ -228,6 +229,7 @@ public Diet() { Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(ex, rng); Set g = new HashSet<>(); + final Set allSolutions = new HashSet<>(); for (int it = 0; it < 10; it++) { System.out.printf("Run n.%,d\n", it); @@ -235,6 +237,7 @@ public Diet() { ga.run(); final Map scores = ga.getState().scores(); + allSolutions.addAll(scores.keySet()); scores.entrySet().stream() .sorted(Map.Entry.comparingByValue()) .limit(10) @@ -253,10 +256,19 @@ public Diet() { System.out.println(); } - while (!ex.isShutdown()) { + final long end = System.nanoTime(); + + System.out.printf("\n%,d solutions evaluated\n", allSolutions.size()); + System.out.printf("Total search time: %.3f seconds\n", (double) (end - beginning) / 1_000_000_000.0); + + if (!ex.isShutdown()) { ex.shutdown(); + } + while (!ex.isTerminated()) { try { - ex.awaitTermination(1, TimeUnit.SECONDS); + if (ex.awaitTermination(1, TimeUnit.SECONDS)) { + break; + } } catch (final InterruptedException ignored) { } } diff --git a/examples/src/main/java/com/ledmington/gal/examples/GeneticTsp.java b/examples/src/main/java/com/ledmington/gal/examples/GeneticTsp.java index a4259f5..357c221 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/GeneticTsp.java +++ b/examples/src/main/java/com/ledmington/gal/examples/GeneticTsp.java @@ -17,13 +17,23 @@ */ package com.ledmington.gal.examples; -import java.util.Arrays; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; +import java.util.stream.Collectors; import com.ledmington.gal.GeneticAlgorithm; import com.ledmington.gal.GeneticAlgorithmConfig; -import com.ledmington.gal.SerialGeneticAlgorithm; +import com.ledmington.gal.ParallelGeneticAlgorithm; public final class GeneticTsp { @@ -42,22 +52,109 @@ private static void shuffle(int[] arr) { } } - private record Solution(int[] array) {} + private static final class Solution { - public GeneticTsp() { + private final int[] array; + private final int cachedHashCode; + + public Solution(final int[] v) { + this.array = Objects.requireNonNull(v); + + int h = 17; + for (final int j : v) { + h = 31 * h + j; + } + cachedHashCode = h; + } + + public int[] array() { + return array; + } + + public int get(final int i) { + return array[i]; + } + + public int hashCode() { + return cachedHashCode; + } + + public boolean equals(final Object other) { + if (other == null) { + return false; + } + if (this == other) { + return true; + } + if (!this.getClass().equals(other.getClass())) { + return false; + } + final Solution s = (Solution) other; + if (s.array.length != this.array.length) { + return false; + } + for (int i = 0; i < array.length; i++) { + if (this.array[i] != s.array[i]) { + return false; + } + } + return true; + } + + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean firstElement = true; + for (final int j : array) { + if (firstElement) { + firstElement = false; + } else { + sb.append(", "); + } + sb.append(String.format("%2d", j)); + } + sb.append("]"); + return sb.toString(); + } + } + + private static BigInteger factorial(BigInteger n) { + if (n.compareTo(BigInteger.ZERO) == 0 || n.compareTo(BigInteger.ONE) == 0) { + return BigInteger.ONE; + } + BigInteger x = BigInteger.ONE; + while (n.compareTo(BigInteger.ZERO) > 0) { + x = x.multiply(n); + n = n.subtract(BigInteger.ONE); + } + return x; + } - final int nCities = 20; + public GeneticTsp() { + final long beginning = System.nanoTime(); + final int nCities = 30; final double[][] coordinates = new double[2][nCities]; + System.out.println("Traveling Salesman Problem's data:"); + System.out.printf("Number of cities : %,d\n", nCities); + System.out.printf( + "Number of unique paths : %.3e\n", + new BigDecimal(factorial(BigInteger.valueOf(nCities - 1)).divide(BigInteger.TWO))); + + System.out.println(); + System.out.println("Cities coordinates:"); for (int i = 0; i < nCities; i++) { coordinates[0][i] = rng.nextDouble(-10.0, 10.0); coordinates[1][i] = rng.nextDouble(-10.0, 10.0); + System.out.printf("%2d: (%+.3f; %+.3f)\n", i, coordinates[0][i], coordinates[1][i]); } + System.out.println(); final double[][] distances = new double[nCities][nCities]; for (int i = 0; i < nCities; i++) { for (int j = 0; j < nCities; j++) { + // euclidean distance final double dist = Math.hypot(coordinates[0][i] - coordinates[0][j], coordinates[1][i] - coordinates[1][j]); distances[i][j] = dist; @@ -65,77 +162,97 @@ public GeneticTsp() { } } - final GeneticAlgorithmConfig config = GeneticAlgorithmConfig.builder() - .populationSize(100) - .survivalRate(0.2) - .mutationRate(0.2) - .crossoverRate(0.1) - .creation(() -> { - final int[] v = new int[nCities]; - for (int i = 0; i < nCities; i++) { - v[i] = i; - } - shuffle(v); - return new Solution(v); - }) - .crossover((a, b) -> { - final int[] result = new int[nCities]; - - for (int i = 0; i < nCities; i++) { - final double choice = rng.nextDouble(0.0, 1.0); - if (choice < 0.45) { - result[i] = a.array()[i]; - } else if (choice < 0.9) { - result[i] = b.array()[i]; - } else { - result[i] = rng.nextInt(0, nCities); - } - } - - int mistakes; - // we create a new input randomly mixing the two parents, until we get one that is valid - do { - final int i = rng.nextInt(0, nCities); - final double choice = rng.nextDouble(0.0, 1.0); - if (choice < 0.45) { - result[i] = a.array()[i]; - } else if (choice < 0.9) { - result[i] = b.array()[i]; - } else { - result[i] = rng.nextInt(0, nCities); - } - - mistakes = (int) - (nCities - Arrays.stream(result).distinct().count()); - - } while (mistakes > 0); - return new Solution(result); - }) - .mutation(x -> { - final int[] y = new int[nCities]; - System.arraycopy(x.array(), 0, y, 0, nCities); - final int i = rng.nextInt(0, nCities); - int j; - do { - j = rng.nextInt(0, nCities); - } while (i == j); - int tmp = y[i]; - y[i] = y[j]; - y[j] = tmp; - return new Solution(y); - }) - .minimize(x -> { - double s = 0.0; - for (int i = 0; i < nCities; i++) { - s += distances[x.array()[i]][x.array()[(i + 1) % nCities]]; - } - // we return it inverted because it is a maximization algorithm for a minimization problem - return s; - }) - .build(); - - final GeneticAlgorithm ga = new SerialGeneticAlgorithm<>(); - ga.setState(config); - ga.run(); + final Supplier> state = + () -> GeneticAlgorithmConfig.builder() + .populationSize(1_000) + .maxGenerations(100) + .survivalRate(0.2) + .crossoverRate(0.7) + .mutationRate(0.2) + .creation(() -> { + final int[] v = new int[nCities]; + for (int i = 0; i < nCities; i++) { + v[i] = i; + } + shuffle(v); + return new Solution(v); + }) + .crossover((a, b) -> { + final int[] result = new int[nCities]; + + for (int i = 0; i < nCities; i++) { + result[i] = rng.nextBoolean() ? a.get(i) : b.get(i); + } + + return new Solution(result); + }) + .mutation(x -> { + final int[] y = new int[nCities]; + System.arraycopy(x.array(), 0, y, 0, nCities); + final int i = rng.nextInt(0, nCities); + y[i] = rng.nextInt(0, nCities); + return new Solution(y); + }) + .minimize(x -> { + final boolean[] visited = new boolean[nCities]; + int visitedCities = 0; + double s = 0.0; + for (int i = 0; i < nCities; i++) { + if (!visited[x.get(i)]) { + visitedCities++; + } + visited[x.get(i)] = true; + s += distances[x.array()[i]][x.array()[(i + 1) % nCities]]; + } + if (visitedCities < nCities) { + // This solution has not visited all cities, so this solution is not valid: we + // return a really high value (not infinity for now) + // FIXME: allow usage of infinities + return 1_000_000.0; + } + return s; + }); + + final ExecutorService ex = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(ex, rng); + Set g = new HashSet<>(); + final Set allSolutions = new HashSet<>(); + + for (int it = 0; it < 10; it++) { + System.out.printf("Run n.%,d\n", it); + ga.setState(state.get().firstGeneration(g).build()); + ga.run(); + + final Map scores = ga.getState().scores(); + allSolutions.addAll(scores.keySet()); + scores.entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .limit(10) + .forEach(e -> System.out.printf("%s -> %f\n", e.getKey(), e.getValue())); + g = scores.entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .limit(10) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + System.out.println(); + } + + final long end = System.nanoTime(); + + System.out.printf("\n%,d solutions evaluated\n", allSolutions.size()); + System.out.printf("Total search time: %.3f seconds\n", (double) (end - beginning) / 1_000_000_000.0); + + if (!ex.isShutdown()) { + ex.shutdown(); + } + while (!ex.isTerminated()) { + try { + if (ex.awaitTermination(1, TimeUnit.SECONDS)) { + break; + } + } catch (final InterruptedException ignored) { + } + } } } diff --git a/examples/src/main/java/com/ledmington/gal/examples/Knapsack.java b/examples/src/main/java/com/ledmington/gal/examples/Knapsack.java index 45deae8..f844f29 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/Knapsack.java +++ b/examples/src/main/java/com/ledmington/gal/examples/Knapsack.java @@ -17,8 +17,18 @@ */ package com.ledmington.gal.examples; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; +import java.util.stream.Collectors; import com.ledmington.gal.GeneticAlgorithm; import com.ledmington.gal.GeneticAlgorithmConfig; @@ -39,6 +49,10 @@ public Solution(final boolean[] array) { cachedHashCode = h; } + public boolean get(final int i) { + return array[i]; + } + public boolean[] array() { return array; } @@ -68,110 +82,149 @@ public boolean equals(final Object other) { } return true; } + + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("["); + boolean firstElement = true; + for (int i = 0; i < array.length; i++) { + if (array[i]) { + if (firstElement) { + firstElement = false; + } else { + sb.append(", "); + } + sb.append(String.format("%2d", i)); + } + } + sb.append("]"); + return sb.toString(); + } } public Knapsack() { + final long beginning = System.nanoTime(); final RandomGenerator rng = RandomGeneratorFactory.getDefault().create(System.nanoTime()); final int nItems = 100; - final int[] weights = new int[nItems]; + final double[] weights = new double[nItems]; final double[] values = new double[nItems]; - final int capacity = 30; + final double capacity = 20.0; + + System.out.println("Knapsack data:"); + System.out.printf("Knapsack's capacity : %.3f\n", capacity); + System.out.printf("Number of items : %,d\n", nItems); + System.out.printf("Total possible solutions : %.3e\n", new BigDecimal(BigInteger.ONE.shiftLeft(nItems))); + System.out.println(); + System.out.println("Items data:"); for (int i = 0; i < nItems; i++) { - weights[i] = rng.nextInt(1, 6); + weights[i] = rng.nextDouble(0.1, 6.0); values[i] = rng.nextDouble(0.1, 6.0); + System.out.printf("%3d: (w: %.3f; v: %.3f)\n", i, weights[i], values[i]); } - - final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(); - - ga.setState(GeneticAlgorithmConfig.builder() - .populationSize(10_000) - .maxGenerations(100) - .survivalRate(0.1) - .crossoverRate(0.8) - .mutationRate(0.1) - .creation(() -> { - final boolean[] v = new boolean[nItems]; - int c = 0; - while (c < capacity) { - int toBeAdded; - do { - toBeAdded = rng.nextInt(0, nItems); - } while (v[toBeAdded]); - - if (c + weights[toBeAdded] > capacity) { - break; - } - - v[toBeAdded] = true; - c += weights[toBeAdded]; - } - return new Solution(v); - }) - .crossover((a, b) -> { - // OR the parents together - final boolean[] v = new boolean[nItems]; - int c = 0; - for (int i = 0; i < nItems; i++) { - if (a.array()[i] || b.array()[i]) { - v[i] = true; - c += weights[i]; + System.out.println(); + + final Supplier> state = + () -> GeneticAlgorithmConfig.builder() + .populationSize(1_000) + .maxGenerations(100) + .survivalRate(0.1) + .crossoverRate(0.7) + .mutationRate(0.2) + .creation(() -> { + final boolean[] v = new boolean[nItems]; + + for (int i = 0; i < nItems; i++) { + v[i] = rng.nextBoolean(); + } + + return new Solution(v); + }) + .crossover((a, b) -> { + final boolean[] v = new boolean[nItems]; + + for (int i = 0; i < nItems; i++) { + v[i] = rng.nextBoolean() ? a.get(i) : b.get(i); + } + + return new Solution(v); + }) + .mutation(x -> { + final boolean[] v = new boolean[nItems]; + System.arraycopy(x.array(), 0, v, 0, nItems); + final int idx = rng.nextInt(0, nItems); + v[idx] = !v[idx]; + return new Solution(v); + }) + .maximize(x -> { + double totalWeight = 0; + double s = 0.0; + for (int i = 0; i < nItems; i++) { + if (x.get(i)) { + s += values[i]; + totalWeight += weights[i]; + } + } + + if (totalWeight > capacity) { + // if the solution is not valid, we give a negative score so that this solution + // will always be considered worse than a valid one but it would still be + // possible to sort invalid solutions + return capacity - totalWeight; + } + return s; + }); + + final ExecutorService ex = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(ex, rng); + Set g = new HashSet<>(); + final Set allSolutions = new HashSet<>(); + + for (int it = 0; it < 10; it++) { + System.out.printf("Run n.%,d\n", it); + ga.setState(state.get().firstGeneration(g).build()); + ga.run(); + + final Map scores = ga.getState().scores(); + allSolutions.addAll(scores.keySet()); + scores.entrySet().stream() + .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) + .limit(10) + .forEach(e -> { + double totalWeight = 0; + for (int i = 0; i < nItems; i++) { + if (e.getKey().get(i)) { + totalWeight += weights[i]; + } } - } + System.out.printf( + "%s -> (total-weight: %.3f; total-value: %.3f)\n", + e.getKey(), totalWeight, e.getValue()); + }); + g = scores.entrySet().stream() + .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) + .limit(10) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + System.out.println(); + } - // randomly remove items until the capacity is valid - while (c > capacity) { - int toBeRemoved; - do { - toBeRemoved = rng.nextInt(0, nItems); - } while (!v[toBeRemoved]); - v[toBeRemoved] = false; - c -= weights[toBeRemoved]; - } + final long end = System.nanoTime(); - return new Solution(v); - }) - .mutation(x -> { - final boolean[] v = new boolean[nItems]; - System.arraycopy(x.array(), 0, v, 0, nItems); - // compute capacity - int c = 0; - for (int i = 0; i < nItems; i++) { - if (v[i]) { - c += weights[i]; - } - } - - // add a random item - int toBeAdded; - do { - toBeAdded = rng.nextInt(0, nItems); - } while (v[toBeAdded]); - v[toBeAdded] = true; - c += weights[toBeAdded]; - - // remove random items until the capacity is valid - while (c > capacity) { - int toBeRemoved; - do { - toBeRemoved = rng.nextInt(0, nItems); - } while (toBeAdded != toBeRemoved && !v[toBeRemoved]); - v[toBeRemoved] = false; - c -= weights[toBeRemoved]; - } + System.out.printf("\n%,d solutions evaluated\n", allSolutions.size()); + System.out.printf("Total search time: %.3f seconds\n", (double) (end - beginning) / 1_000_000_000.0); - return new Solution(v); - }) - .maximize(x -> { - double s = 0.0; - for (int i = 0; i < nItems; i++) { - if (x.array()[i]) { - s += values[i]; - } - } - return s; - }) - .build()); - ga.run(); + if (!ex.isShutdown()) { + ex.shutdown(); + } + while (!ex.isTerminated()) { + try { + if (ex.awaitTermination(1, TimeUnit.SECONDS)) { + break; + } + } catch (final InterruptedException ignored) { + } + } } } diff --git a/examples/src/main/java/com/ledmington/gal/examples/Main.java b/examples/src/main/java/com/ledmington/gal/examples/Main.java index 926a2af..10fae3a 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/Main.java +++ b/examples/src/main/java/com/ledmington/gal/examples/Main.java @@ -25,7 +25,7 @@ public final class Main { private static final Map examples = Map.of( "GeneticTsp", GeneticTsp::new, - "RandomString", + "RandomStrings", RandomStrings::new, "Knapsack", Knapsack::new, diff --git a/examples/src/main/java/com/ledmington/gal/examples/NeuralNetwork.java b/examples/src/main/java/com/ledmington/gal/examples/NeuralNetwork.java index 03412c8..0505dda 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/NeuralNetwork.java +++ b/examples/src/main/java/com/ledmington/gal/examples/NeuralNetwork.java @@ -17,6 +17,9 @@ */ package com.ledmington.gal.examples; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; @@ -179,11 +182,13 @@ public NeuralNetwork() { } } - final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(); + final ExecutorService ex = + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + final GeneticAlgorithm ga = new ParallelGeneticAlgorithm<>(ex, rng); ga.setState(GeneticAlgorithmConfig.builder() + .populationSize(1_000) .maxGenerations(100) - .populationSize(1000) .survivalRate(0.1) .crossoverRate(0.8) .mutationRate(0.1) @@ -244,5 +249,17 @@ public NeuralNetwork() { }) .build()); ga.run(); + + if (!ex.isShutdown()) { + ex.shutdown(); + } + while (!ex.isTerminated()) { + try { + if (ex.awaitTermination(1, TimeUnit.SECONDS)) { + break; + } + } catch (final InterruptedException ignored) { + } + } } } diff --git a/examples/src/main/java/com/ledmington/gal/examples/RandomStrings.java b/examples/src/main/java/com/ledmington/gal/examples/RandomStrings.java index 584c777..8b51fa2 100644 --- a/examples/src/main/java/com/ledmington/gal/examples/RandomStrings.java +++ b/examples/src/main/java/com/ledmington/gal/examples/RandomStrings.java @@ -17,11 +17,13 @@ */ package com.ledmington.gal.examples; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.random.RandomGenerator; import java.util.random.RandomGeneratorFactory; import java.util.stream.Collectors; -import java.util.stream.Stream; import com.ledmington.gal.GeneticAlgorithm; import com.ledmington.gal.GeneticAlgorithmConfig; @@ -29,58 +31,83 @@ public final class RandomStrings { public RandomStrings() { + final long beginning = System.nanoTime(); final RandomGenerator rng = RandomGeneratorFactory.getDefault().create(System.nanoTime()); - final String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ !"; - final String targetString = "I love Genetic Algorithms!"; - final int length = targetString.length(); + final String alphabet = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,:;-_@#[]{}()!?='\"+*/"; + final String targetString = + "This library for Genetic Algorithms is absolutely fantastic! I cannot wait to try and use it with Java 17 and Gradle 8.7! Now let's write another time just to have longer strings and, therefore, add artificial complexity to the problem."; + final int targetLength = targetString.length(); final Supplier randomChar = () -> alphabet.charAt(rng.nextInt(0, alphabet.length())); - final GeneticAlgorithm ga = new SerialGeneticAlgorithm<>(); - ga.setState(GeneticAlgorithmConfig.builder() - .populationSize(1000) - .survivalRate(0.1) - .crossoverRate(0.7) - .mutationRate(0.01) - .maxGenerations(1000) - .creation(() -> Stream.generate(randomChar) - .limit(length) - .map(Object::toString) - .collect(Collectors.joining())) - .crossover((a, b) -> { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < targetString.length(); i++) { - final double choice = rng.nextDouble(0.0, 1.0); - if (choice < 0.45) { - // take char from parent A - sb.append(a.charAt(i)); - } else if (choice < 0.9) { - // take char from parent B - sb.append(b.charAt(i)); - } else { - // add random character - sb.append(randomChar.get()); - } - } - return sb.toString(); - }) - .mutation(s -> { - final int idx = rng.nextInt(0, length); - return s.substring(0, idx) + randomChar.get() + s.substring(idx + 1, length); - }) - .maximize(s -> { - if (s.length() != length) { - throw new IllegalArgumentException( - String.format("Invalid length: was %d but should have been %d", s.length(), length)); - } - int count = 0; - for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) == targetString.charAt(i)) { - count++; - } - } - return (double) count; - }) - .build()); - ga.run(); + final Supplier> state = + () -> GeneticAlgorithmConfig.builder() + .populationSize(1_000) + .maxGenerations(100) + .survivalRate(0.1) + .crossoverRate(0.7) + .mutationRate(0.2) + .creation(() -> { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < targetLength; i++) { + sb.append(randomChar.get()); + } + return sb.toString(); + }) + .crossover((a, b) -> { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < targetLength; i++) { + sb.append(rng.nextBoolean() ? a.charAt(i) : b.charAt(i)); + } + return sb.toString(); + }) + .mutation(s -> { + final int idx = rng.nextInt(0, targetLength); + return s.substring(0, idx) + randomChar.get() + s.substring(idx + 1, targetLength); + }) + .maximize(s -> { + if (s.length() != targetLength) { + throw new IllegalArgumentException(String.format( + "Invalid length: was %d but should have been %d", s.length(), targetLength)); + } + int count = 0; + for (int i = 0; i < targetLength; i++) { + if (s.charAt(i) == targetString.charAt(i)) { + count++; + } + } + return (double) count; + }) + .stopCriterion(s -> s.equals(targetString)); + + final GeneticAlgorithm ga = new SerialGeneticAlgorithm<>(rng); + Set g = new HashSet<>(); + final Set allSolutions = new HashSet<>(); + + for (int it = 0; it < 10; it++) { + System.out.printf("Run n.%,d\n", it); + ga.setState(state.get().firstGeneration(g).build()); + ga.run(); + + final Map scores = ga.getState().scores(); + allSolutions.addAll(scores.keySet()); + scores.entrySet().stream() + .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) + .limit(10) + .forEach(e -> { + System.out.printf("%s -> %f\n", e.getKey(), e.getValue()); + }); + g = scores.entrySet().stream() + .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) + .limit(10) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + System.out.println(); + } + + final long end = System.nanoTime(); + + System.out.printf("\n%,d solutions evaluated\n", allSolutions.size()); + System.out.printf("Total search time: %.3f seconds\n", (double) (end - beginning) / 1_000_000_000.0); } }