Merge remote-tracking branch 'upstream/master'

This commit is contained in:
andrebrowne
2020-06-13 13:55:41 -04:00
1508 changed files with 26124 additions and 4324 deletions
@@ -0,0 +1,208 @@
package com.baeldung.algorithms.play2048;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Board {
private static final Logger LOG = LoggerFactory.getLogger(Board.class);
private final int[][] board;
private final int score;
public Board(int size) {
assert(size > 0);
this.board = new int[size][];
this.score = 0;
for (int x = 0; x < size; ++x) {
this.board[x] = new int[size];
for (int y = 0; y < size; ++y) {
board[x][y] = 0;
}
}
}
private Board(int[][] board, int score) {
this.score = score;
this.board = new int[board.length][];
for (int x = 0; x < board.length; ++x) {
this.board[x] = Arrays.copyOf(board[x], board[x].length);
}
}
public int getSize() {
return board.length;
}
public int getScore() {
return score;
}
public int getCell(Cell cell) {
int x = cell.getX();
int y = cell.getY();
assert(x >= 0 && x < board.length);
assert(y >= 0 && y < board.length);
return board[x][y];
}
public boolean isEmpty(Cell cell) {
return getCell(cell) == 0;
}
public List<Cell> emptyCells() {
List<Cell> result = new ArrayList<>();
for (int x = 0; x < board.length; ++x) {
for (int y = 0; y < board[x].length; ++y) {
Cell cell = new Cell(x, y);
if (isEmpty(cell)) {
result.add(cell);
}
}
}
return result;
}
public Board placeTile(Cell cell, int number) {
if (!isEmpty(cell)) {
throw new IllegalArgumentException("That cell is not empty");
}
Board result = new Board(this.board, this.score);
result.board[cell.getX()][cell.getY()] = number;
return result;
}
public Board move(Move move) {
// Clone the board
int[][] tiles = new int[this.board.length][];
for (int x = 0; x < this.board.length; ++x) {
tiles[x] = Arrays.copyOf(this.board[x], this.board[x].length);
}
LOG.debug("Before move: {}", Arrays.deepToString(tiles));
// If we're doing an Left/Right move then transpose the board to make it a Up/Down move
if (move == Move.LEFT || move == Move.RIGHT) {
tiles = transpose(tiles);
LOG.debug("After transpose: {}", Arrays.deepToString(tiles));
}
// If we're doing a Right/Down move then reverse the board.
// With the above we're now always doing an Up move
if (move == Move.DOWN || move == Move.RIGHT) {
tiles = reverse(tiles);
LOG.debug("After reverse: {}", Arrays.deepToString(tiles));
}
LOG.debug("Ready to move: {}", Arrays.deepToString(tiles));
// Shift everything up
int[][] result = new int[tiles.length][];
int newScore = 0;
for (int x = 0; x < tiles.length; ++x) {
LinkedList<Integer> thisRow = new LinkedList<>();
for (int y = 0; y < tiles[0].length; ++y) {
if (tiles[x][y] > 0) {
thisRow.add(tiles[x][y]);
}
}
LOG.debug("Unmerged row: {}", thisRow);
LinkedList<Integer> newRow = new LinkedList<>();
while (thisRow.size() >= 2) {
int first = thisRow.pop();
int second = thisRow.peek();
LOG.debug("Looking at numbers {} and {}", first, second);
if (second == first) {
LOG.debug("Numbers match, combining");
int newNumber = first * 2;
newRow.add(newNumber);
newScore += newNumber;
thisRow.pop();
} else {
LOG.debug("Numbers don't match");
newRow.add(first);
}
}
newRow.addAll(thisRow);
LOG.debug("Merged row: {}", newRow);
result[x] = new int[tiles[0].length];
for (int y = 0; y < tiles[0].length; ++y) {
if (newRow.isEmpty()) {
result[x][y] = 0;
} else {
result[x][y] = newRow.pop();
}
}
}
LOG.debug("After moves: {}", Arrays.deepToString(result));
// Un-reverse the board
if (move == Move.DOWN || move == Move.RIGHT) {
result = reverse(result);
LOG.debug("After reverse: {}", Arrays.deepToString(result));
}
// Un-transpose the board
if (move == Move.LEFT || move == Move.RIGHT) {
result = transpose(result);
LOG.debug("After transpose: {}", Arrays.deepToString(result));
}
return new Board(result, this.score + newScore);
}
private static int[][] transpose(int[][] input) {
int[][] result = new int[input.length][];
for (int x = 0; x < input.length; ++x) {
result[x] = new int[input[0].length];
for (int y = 0; y < input[0].length; ++y) {
result[x][y] = input[y][x];
}
}
return result;
}
private static int[][] reverse(int[][] input) {
int[][] result = new int[input.length][];
for (int x = 0; x < input.length; ++x) {
result[x] = new int[input[0].length];
for (int y = 0; y < input[0].length; ++y) {
result[x][y] = input[x][input.length - y - 1];
}
}
return result;
}
@Override
public String toString() {
return Arrays.deepToString(board);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Board board1 = (Board) o;
return Arrays.deepEquals(board, board1.board);
}
@Override
public int hashCode() {
return Arrays.deepHashCode(board);
}
}
@@ -0,0 +1,26 @@
package com.baeldung.algorithms.play2048;
import java.util.StringJoiner;
public class Cell {
private int x;
private int y;
public Cell(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return new StringJoiner(", ", Cell.class.getSimpleName() + "[", "]").add("x=" + x).add("y=" + y).toString();
}
}
@@ -0,0 +1,27 @@
package com.baeldung.algorithms.play2048;
import java.security.SecureRandom;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Computer {
private static final Logger LOG = LoggerFactory.getLogger(Computer.class);
private final SecureRandom rng = new SecureRandom();
public Board makeMove(Board input) {
List<Cell> emptyCells = input.emptyCells();
LOG.info("Number of empty cells: {}", emptyCells.size());
double numberToPlace = rng.nextDouble();
LOG.info("New number probability: {}", numberToPlace);
int indexToPlace = rng.nextInt(emptyCells.size());
Cell cellToPlace = emptyCells.get(indexToPlace);
LOG.info("Placing number into empty cell: {}", cellToPlace);
return input.placeTile(cellToPlace, numberToPlace >= 0.9 ? 4 : 2);
}
}
@@ -0,0 +1,126 @@
package com.baeldung.algorithms.play2048;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Human {
private static final Logger LOG = LoggerFactory.getLogger(Human.class);
public Board makeMove(Board input) {
// For each move in MOVE
// Generate board from move
// Generate Score for Board
// Return board with the best score
//
// Generate Score
// If Depth Limit
// Return Final Score
// Total Score = 0
// For every empty square in new board
// Generate board with "2" in square
// Calculate Score
// Total Score += (Score * 0.9)
// Generate board with "4" in square
// Calculate Score
// Total Score += (Score * 0.1)
//
// Calculate Score
// For each move in MOVE
// Generate board from move
// Generate score for board
// Return the best generated score
return Arrays.stream(Move.values())
.parallel()
.map(input::move)
.filter(board -> !board.equals(input))
.max(Comparator.comparingInt(board -> generateScore(board, 0)))
.orElse(input);
}
private int generateScore(Board board, int depth) {
if (depth >= 3) {
int finalScore = calculateFinalScore(board);
LOG.debug("Final score for board {}: {}", board,finalScore);
return finalScore;
}
return board.emptyCells().stream()
.parallel()
.flatMap(cell -> Stream.of(new Pair<>(cell, 2), new Pair<>(cell, 4)))
.mapToInt(move -> {
LOG.debug("Simulating move {} at depth {}", move, depth);
Board newBoard = board.placeTile(move.getFirst(), move.getSecond());
int boardScore = calculateScore(newBoard, depth + 1);
int calculatedScore = (int) (boardScore * (move.getSecond() == 2 ? 0.9 : 0.1));
LOG.debug("Calculated score for board {} and move {} at depth {}: {}", newBoard, move, depth, calculatedScore);
return calculatedScore;
})
.sum();
}
private int calculateScore(Board board, int depth) {
return Arrays.stream(Move.values())
.parallel()
.map(board::move)
.filter(moved -> !moved.equals(board))
.mapToInt(newBoard -> generateScore(newBoard, depth))
.max()
.orElse(0);
}
private int calculateFinalScore(Board board) {
List<List<Integer>> rowsToScore = new ArrayList<>();
for (int i = 0; i < board.getSize(); ++i) {
List<Integer> row = new ArrayList<>();
List<Integer> col = new ArrayList<>();
for (int j = 0; j < board.getSize(); ++j) {
row.add(board.getCell(new Cell(i, j)));
col.add(board.getCell(new Cell(j, i)));
}
rowsToScore.add(row);
rowsToScore.add(col);
}
return rowsToScore.stream()
.parallel()
.mapToInt(row -> {
List<Integer> preMerged = row.stream()
.filter(value -> value != 0)
.collect(Collectors.toList());
int numMerges = 0;
int monotonicityLeft = 0;
int monotonicityRight = 0;
for (int i = 0; i < preMerged.size() - 1; ++i) {
Integer first = preMerged.get(i);
Integer second = preMerged.get(i + 1);
if (first.equals(second)) {
++numMerges;
} else if (first > second) {
monotonicityLeft += first - second;
} else {
monotonicityRight += second - first;
}
}
int score = 1000;
score += 250 * row.stream().filter(value -> value == 0).count();
score += 750 * numMerges;
score -= 10 * row.stream().mapToInt(value -> value).sum();
score -= 50 * Math.min(monotonicityLeft, monotonicityRight);
return score;
})
.sum();
}
}
@@ -0,0 +1,8 @@
package com.baeldung.algorithms.play2048;
public enum Move {
UP,
DOWN,
LEFT,
RIGHT
}
@@ -0,0 +1,73 @@
package com.baeldung.algorithms.play2048;
public class Play2048 {
private static final int SIZE = 3;
private static final int INITIAL_NUMBERS = 2;
public static void main(String[] args) {
// The board and players
Board board = new Board(SIZE);
Computer computer = new Computer();
Human human = new Human();
// The computer has two moves first
System.out.println("Setup");
System.out.println("=====");
for (int i = 0; i < INITIAL_NUMBERS; ++i) {
board = computer.makeMove(board);
}
printBoard(board);
do {
board = human.makeMove(board);
System.out.println("Human move");
System.out.println("==========");
printBoard(board);
board = computer.makeMove(board);
System.out.println("Computer move");
System.out.println("=============");
printBoard(board);
} while (!board.emptyCells().isEmpty());
System.out.println("Final Score: " + board.getScore());
}
private static void printBoard(Board board) {
StringBuilder topLines = new StringBuilder();
StringBuilder midLines = new StringBuilder();
for (int x = 0; x < board.getSize(); ++x) {
topLines.append("+--------");
midLines.append("| ");
}
topLines.append("+");
midLines.append("|");
for (int y = 0; y < board.getSize(); ++y) {
System.out.println(topLines);
System.out.println(midLines);
for (int x = 0; x < board.getSize(); ++x) {
Cell cell = new Cell(x, y);
System.out.print("|");
if (board.isEmpty(cell)) {
System.out.print(" ");
} else {
StringBuilder output = new StringBuilder(Integer.toString(board.getCell(cell)));
while (output.length() < 8) {
output.append(" ");
if (output.length() < 8) {
output.insert(0, " ");
}
}
System.out.print(output);
}
}
System.out.println("|");
System.out.println(midLines);
}
System.out.println(topLines);
System.out.println("Score: " + board.getScore());
}
}
+2 -6
View File
@@ -15,9 +15,5 @@ This module contains articles about algorithms. Some classes of algorithms, e.g.
- [Maximum Subarray Problem](https://www.baeldung.com/java-maximum-subarray)
- [How to Merge Two Sorted Arrays](https://www.baeldung.com/java-merge-sorted-arrays)
- [Median of Stream of Integers using Heap](https://www.baeldung.com/java-stream-integers-median-using-heap)
- [Kruskal’s Algorithm for Spanning Trees](https://www.baeldung.com/java-spanning-trees-kruskal)
- [Balanced Brackets Algorithm in Java](https://www.baeldung.com/java-balanced-brackets-algorithm)
- [Efficiently Merge Sorted Java Sequences](https://www.baeldung.com/java-merge-sorted-sequences)
- [Introduction to Greedy Algorithms with Java](https://www.baeldung.com/java-greedy-algorithms)
- [The Caesar Cipher in Java](https://www.baeldung.com/java-caesar-cipher)
- More articles: [[<-- prev]](/../algorithms-miscellaneous-4)
- More articles: [[<-- prev]](/../algorithms-miscellaneous-4) [[next -->]](/../algorithms-miscellaneous-6)
-6
View File
@@ -25,12 +25,6 @@
<artifactId>commons-math3</artifactId>
<version>${commons-math3.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>pl.allegro.finance</groupId>
<artifactId>tradukisto</artifactId>
+6
View File
@@ -2,3 +2,9 @@
- [Boruvka’s Algorithm for Minimum Spanning Trees](https://www.baeldung.com/java-boruvka-algorithm)
- [Gradient Descent in Java](https://www.baeldung.com/java-gradient-descent)
- [Kruskal’s Algorithm for Spanning Trees](https://www.baeldung.com/java-spanning-trees-kruskal)
- [Balanced Brackets Algorithm in Java](https://www.baeldung.com/java-balanced-brackets-algorithm)
- [Efficiently Merge Sorted Java Sequences](https://www.baeldung.com/java-merge-sorted-sequences)
- [Introduction to Greedy Algorithms with Java](https://www.baeldung.com/java-greedy-algorithms)
- [The Caesar Cipher in Java](https://www.baeldung.com/java-caesar-cipher)
- More articles: [[<-- prev]](/../algorithms-miscellaneous-5)
+25
View File
@@ -18,10 +18,35 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${org.assertj.core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>${commons-math3.version}</version>
</dependency>
</dependencies>
<properties>
<guava.version>28.1-jre</guava.version>
<org.assertj.core.version>3.9.0</org.assertj.core.version>
<junit.platform.version>1.6.0</junit.platform.version>
<commons-math3.version>3.6.1</commons-math3.version>
</properties>
</project>
@@ -1,13 +1,13 @@
package com.baeldung.algorithms.minheapmerge;
public class HeapNode {
int element;
int arrayIndex;
int nextElementIndex = 1;
public HeapNode(int element, int arrayIndex) {
this.element = element;
this.arrayIndex = arrayIndex;
}
}
package com.baeldung.algorithms.minheapmerge;
public class HeapNode {
int element;
int arrayIndex;
int nextElementIndex = 1;
public HeapNode(int element, int arrayIndex) {
this.element = element;
this.arrayIndex = arrayIndex;
}
}
@@ -1,88 +1,88 @@
package com.baeldung.algorithms.minheapmerge;
public class MinHeap {
HeapNode[] heapNodes;
public MinHeap(HeapNode heapNodes[]) {
this.heapNodes = heapNodes;
heapifyFromLastLeafsParent();
}
void heapifyFromLastLeafsParent() {
int lastLeafsParentIndex = getParentNodeIndex(heapNodes.length);
while (lastLeafsParentIndex >= 0) {
heapify(lastLeafsParentIndex);
lastLeafsParentIndex--;
}
}
void heapify(int index) {
int leftNodeIndex = getLeftNodeIndex(index);
int rightNodeIndex = getRightNodeIndex(index);
int smallestElementIndex = index;
if (leftNodeIndex < heapNodes.length && heapNodes[leftNodeIndex].element < heapNodes[index].element) {
smallestElementIndex = leftNodeIndex;
}
if (rightNodeIndex < heapNodes.length && heapNodes[rightNodeIndex].element < heapNodes[smallestElementIndex].element) {
smallestElementIndex = rightNodeIndex;
}
if (smallestElementIndex != index) {
swap(index, smallestElementIndex);
heapify(smallestElementIndex);
}
}
int getParentNodeIndex(int index) {
return (index - 1) / 2;
}
int getLeftNodeIndex(int index) {
return (2 * index + 1);
}
int getRightNodeIndex(int index) {
return (2 * index + 2);
}
HeapNode getRootNode() {
return heapNodes[0];
}
void heapifyFromRoot() {
heapify(0);
}
void swap(int i, int j) {
HeapNode temp = heapNodes[i];
heapNodes[i] = heapNodes[j];
heapNodes[j] = temp;
}
static int[] merge(int[][] array) {
HeapNode[] heapNodes = new HeapNode[array.length];
int resultingArraySize = 0;
for (int i = 0; i < array.length; i++) {
HeapNode node = new HeapNode(array[i][0], i);
heapNodes[i] = node;
resultingArraySize += array[i].length;
}
MinHeap minHeap = new MinHeap(heapNodes);
int[] resultingArray = new int[resultingArraySize];
for (int i = 0; i < resultingArraySize; i++) {
HeapNode root = minHeap.getRootNode();
resultingArray[i] = root.element;
if (root.nextElementIndex < array[root.arrayIndex].length) {
root.element = array[root.arrayIndex][root.nextElementIndex++];
} else {
root.element = Integer.MAX_VALUE;
}
minHeap.heapifyFromRoot();
}
return resultingArray;
}
}
package com.baeldung.algorithms.minheapmerge;
public class MinHeap {
HeapNode[] heapNodes;
public MinHeap(HeapNode heapNodes[]) {
this.heapNodes = heapNodes;
heapifyFromLastLeafsParent();
}
void heapifyFromLastLeafsParent() {
int lastLeafsParentIndex = getParentNodeIndex(heapNodes.length);
while (lastLeafsParentIndex >= 0) {
heapify(lastLeafsParentIndex);
lastLeafsParentIndex--;
}
}
void heapify(int index) {
int leftNodeIndex = getLeftNodeIndex(index);
int rightNodeIndex = getRightNodeIndex(index);
int smallestElementIndex = index;
if (leftNodeIndex < heapNodes.length && heapNodes[leftNodeIndex].element < heapNodes[index].element) {
smallestElementIndex = leftNodeIndex;
}
if (rightNodeIndex < heapNodes.length && heapNodes[rightNodeIndex].element < heapNodes[smallestElementIndex].element) {
smallestElementIndex = rightNodeIndex;
}
if (smallestElementIndex != index) {
swap(index, smallestElementIndex);
heapify(smallestElementIndex);
}
}
int getParentNodeIndex(int index) {
return (index - 1) / 2;
}
int getLeftNodeIndex(int index) {
return (2 * index + 1);
}
int getRightNodeIndex(int index) {
return (2 * index + 2);
}
HeapNode getRootNode() {
return heapNodes[0];
}
void heapifyFromRoot() {
heapify(0);
}
void swap(int i, int j) {
HeapNode temp = heapNodes[i];
heapNodes[i] = heapNodes[j];
heapNodes[j] = temp;
}
static int[] merge(int[][] array) {
HeapNode[] heapNodes = new HeapNode[array.length];
int resultingArraySize = 0;
for (int i = 0; i < array.length; i++) {
HeapNode node = new HeapNode(array[i][0], i);
heapNodes[i] = node;
resultingArraySize += array[i].length;
}
MinHeap minHeap = new MinHeap(heapNodes);
int[] resultingArray = new int[resultingArraySize];
for (int i = 0; i < resultingArraySize; i++) {
HeapNode root = minHeap.getRootNode();
resultingArray[i] = root.element;
if (root.nextElementIndex < array[root.arrayIndex].length) {
root.element = array[root.arrayIndex][root.nextElementIndex++];
} else {
root.element = Integer.MAX_VALUE;
}
minHeap.heapifyFromRoot();
}
return resultingArray;
}
}
@@ -1,22 +1,22 @@
package com.baeldung.algorithms.minheapmerge;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class MinHeapUnitTest {
private final int[][] inputArray = { { 0, 6 }, { 1, 5, 10, 100 }, { 2, 4, 200, 650 } };
private final int[] expectedArray = { 0, 1, 2, 4, 5, 6, 10, 100, 200, 650 };
@Test
public void givenSortedArrays_whenMerged_thenShouldReturnASingleSortedarray() {
int[] resultArray = MinHeap.merge(inputArray);
assertThat(resultArray.length, is(equalTo(10)));
assertThat(resultArray, is(equalTo(expectedArray)));
}
}
package com.baeldung.algorithms.minheapmerge;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class MinHeapUnitTest {
private final int[][] inputArray = { { 0, 6 }, { 1, 5, 10, 100 }, { 2, 4, 200, 650 } };
private final int[] expectedArray = { 0, 1, 2, 4, 5, 6, 10, 100, 200, 650 };
@Test
public void givenSortedArrays_whenMerged_thenShouldReturnASingleSortedarray() {
int[] resultArray = MinHeap.merge(inputArray);
assertThat(resultArray.length, is(equalTo(10)));
assertThat(resultArray, is(equalTo(expectedArray)));
}
}

Some files were not shown because too many files have changed in this diff Show More