consumer);
}
diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/binary/BinarySearchTree.java
similarity index 74%
rename from 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java
rename to 2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/binary/BinarySearchTree.java
index 9b821d1d..7a0f5f30 100644
--- a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java
+++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/binary/BinarySearchTree.java
@@ -1,11 +1,12 @@
-package com.bobocode.cs;
+package com.bobocode.cs.binary;
+import com.bobocode.cs.Tree;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
- * {@link RecursiveBinarySearchTree} is an implementation of a {@link BinarySearchTree} that is based on a linked nodes
+ * {@link BinarySearchTree} is an implementation of a {@link Tree} that is based on a linked nodes
* and recursion. A tree node is represented as a nested class {@link Node}. It holds an element (a value) and
* two references to the left and right child nodes.
*
@@ -16,14 +17,14 @@
* @author Taras Boychuk
* @author Maksym Stasiuk
*/
-public class RecursiveBinarySearchTree> implements BinarySearchTree {
+public class BinarySearchTree> implements Tree {
private static class Node {
- T element;
+ T value;
Node left;
Node right;
- private Node(T element) {
- this.element = element;
+ private Node(T value) {
+ this.value = value;
}
public static Node valueOf(T element) {
@@ -34,8 +35,8 @@ public static Node valueOf(T element) {
private Node root;
private int size = 0;
- public static > RecursiveBinarySearchTree of(T... elements) {
- RecursiveBinarySearchTree bst = new RecursiveBinarySearchTree<>();
+ public static > BinarySearchTree of(T... elements) {
+ BinarySearchTree bst = new BinarySearchTree<>();
Stream.of(elements).forEach(bst::insert);
return bst;
}
@@ -60,9 +61,9 @@ boolean insertElement(T element) {
}
private boolean insertIntoSubTree(Node subTreeRoot, T element) {
- if (subTreeRoot.element.compareTo(element) > 0) {
+ if (subTreeRoot.value.compareTo(element) > 0) {
return insertIntoLeftSubtree(subTreeRoot, element);
- } else if (subTreeRoot.element.compareTo(element) < 0) {
+ } else if (subTreeRoot.value.compareTo(element) < 0) {
return insertIntoRightSubtree(subTreeRoot, element);
} else {
return false;
@@ -97,9 +98,9 @@ public boolean contains(T element) {
private Node findChildNodeByElement(Node node, T element) {
if (node == null) {
return null;
- } else if (node.element.compareTo(element) > 0) {
+ } else if (node.value.compareTo(element) > 0) {
return findChildNodeByElement(node.left, element);
- } else if (node.element.compareTo(element) < 0) {
+ } else if (node.value.compareTo(element) < 0) {
return findChildNodeByElement(node.right, element);
} else {
return node;
@@ -129,10 +130,21 @@ public void inOrderTraversal(Consumer consumer) {
inOrderTraversal(root, consumer);
}
+ @Override
+ public void preOrderTraversal(Consumer consumer) {
+ preOrderTraversal(root, consumer);
+ }
+ private void preOrderTraversal(Node node, Consumer consumer){
+ if (node != null){
+ consumer.accept(node.value);
+ preOrderTraversal(node.left, consumer);
+ preOrderTraversal(node.right, consumer);
+ }
+ }
private void inOrderTraversal(Node node, Consumer consumer) {
if (node != null) {
inOrderTraversal(node.left, consumer);
- consumer.accept(node.element);
+ consumer.accept(node.value);
inOrderTraversal(node.right, consumer);
}
}
diff --git a/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/redblack/RedBlackSearchTree.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/redblack/RedBlackSearchTree.java
new file mode 100644
index 00000000..c9f530ae
--- /dev/null
+++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/redblack/RedBlackSearchTree.java
@@ -0,0 +1,320 @@
+package com.bobocode.cs.redblack;
+
+import com.bobocode.cs.Tree;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.ToString;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+/**
+ * {@link RedBlackSearchTree} is an implementation of a {@link Tree} that is based on a linked nodes
+ * and recursion. A tree node is represented as a nested class {@link RedBlackSearchTree.Node}. It holds an element (a value),
+ * three references to the left, right and parent nodes. Also, {@link RedBlackSearchTree.Node} has a boolean color field where RED is true and BLACK is false.
+ * To understand Red Black tree better following resources can be used
+ *
+ *
+ * @param a type of elements that are stored in the tree
+ * @author Taras Boychuk
+ * @author Yuriy Fomin
+ */
+public class RedBlackSearchTree> implements Tree {
+
+ private static final boolean RED = true;
+ private static final boolean BLACK = false;
+
+ private int size;
+ private Node root;
+
+ public static > RedBlackSearchTree of(T... elements) {
+ RedBlackSearchTree redBlackSearchTree = new RedBlackSearchTree<>();
+ Stream.of(elements).forEach(redBlackSearchTree::insert);
+ return redBlackSearchTree;
+ }
+
+ @Override
+ public boolean insert(@NonNull T element) {
+ Node newNode = insertHelper(this.root, element);
+ if (newNode != null) {
+ this.size += 1;
+ repair(newNode);
+ }
+ this.root.setColor(BLACK);
+ return newNode != null;
+ }
+
+ private Node insertHelper(Node node, T value) {
+ if (this.root == null) {
+ this.root = new Node<>(value, null);
+ return this.root;
+ }
+ if (node == null) {
+ return null;
+ }
+ int res = value.compareTo(node.getValue());
+ if (res < 0 && node.getLeft() == null) {
+ node.setLeft(new Node<>(value, node));
+ return node.getLeft();
+ }
+ if (res > 0 && node.getRight() == null) {
+ node.setRight(new Node<>(value, node));
+ return node.getRight();
+ }
+
+ if (res < 0) {
+ return insertHelper(node.getLeft(), value);
+ }
+ if (res > 0) {
+ return insertHelper(node.getRight(), value);
+ }
+ return null;
+ }
+
+ private void repair(Node node) {
+ if (node == null) {
+ return;
+ }
+ if (node.color == BLACK){
+ return;
+ }
+ if (node.getParent() == null) {
+ return;
+ }
+ if (node.getParent().color == BLACK) {
+ return;
+ }
+ if (node.getGrandParent() == null) {
+ return;
+ }
+ Node grandParent = node.getGrandParent();
+ Node parent = node.getParent();
+ Node uncle = getUncle(parent);
+ // case 1 uncle is red -> recolor
+ boolean uncleColorIsRed = uncle != null && uncle.color == RED;
+ if (uncleColorIsRed) {
+ parent.setColor(BLACK);
+ grandParent.setColor(RED);
+ uncle.setColor(BLACK);
+ repair(node.getGrandParent());
+ return;
+ }
+ // case 2 uncle is BLACK and right triangle is formed -> rotate to right parent
+ boolean uncleColorIsBlack = uncle == null || uncle.color == BLACK;
+ if (uncleColorIsBlack && childAndParentFormTriangle(parent, node) && parent.getLeft() == node) {
+ rotateRight(parent);
+ repair(parent);
+ return;
+ }
+ // case 2 uncle is BLACK and left triangle is formed -> rotate to left parent
+ if (uncleColorIsBlack && childAndParentFormTriangle(parent, node) && parent.getRight() == node) {
+ rotateLeft(parent);
+ repair(parent);
+ return;
+ }
+ // case 3 uncle is BLACK and flat line is formed to the right -> rotate grandparent to right and recolor parent and grandparent
+ if (uncleColorIsBlack && childAndParentFormFlatLine(parent, node) && parent.getLeft() == node) {
+ rotateRight(grandParent);
+ grandParent.setColor(RED);
+ parent.setColor(BLACK);
+ repair(node.getParent());
+ return;
+ }
+
+ // case 3 uncle is BLACK and flat line is formed to the left -> rotate grandparent to right and recolor parent and grandparent
+ if (uncleColorIsBlack && childAndParentFormFlatLine(parent, node) && parent.getRight() == node) {
+ rotateLeft(grandParent);
+ grandParent.setColor(RED);
+ parent.setColor(BLACK);
+ repair(node.getParent());
+ return;
+ }
+ repair(node.getParent());
+ }
+
+ private boolean childAndParentFormTriangle(Node parent, Node child) {
+ if (parent == null) {
+ return false;
+ }
+ if (child == null) {
+ return false;
+ }
+ if (parent.getParent() == null) {
+ return false;
+ }
+ Node grandParent = parent.getParent();
+ if (parent.getLeft() == child && grandParent.getRight() == parent) {
+ return true;
+ }
+ return parent.getRight() == child && grandParent.getLeft() == parent;
+ }
+
+ private boolean childAndParentFormFlatLine(Node parent, Node child) {
+ if (parent == null) {
+ return false;
+ }
+ if (child == null) {
+ return false;
+ }
+ if (parent.getParent() == null) {
+ return false;
+ }
+ Node grandParent = parent.getParent();
+ if (grandParent.getRight() == parent && parent.getRight() == child) {
+ return true;
+ }
+ return grandParent.getLeft() == parent && parent.getLeft() == child;
+ }
+
+ private void rotateRight(@NonNull Node node) {
+ Node leftChild = node.getLeft();
+ Node parent = node.getParent();
+ node.setLeft(null);
+ Node leftRightChild = leftChild == null ? null : leftChild.getRight();
+ if (parent != null && parent.getRight() == node) {
+ parent.setRight(leftChild);
+ }
+
+ if (parent != null && parent.getLeft() == node) {
+ parent.setLeft(leftChild);
+ }
+
+ if (leftChild != null) {
+ leftChild.setRight(node);
+ leftChild.setParent(node.getParent());
+ }
+ if (node == this.root) {
+ this.root = leftChild;
+ }
+ node.setParent(leftChild);
+ node.setLeft(leftRightChild);
+ if (leftRightChild != null) {
+ leftRightChild.setParent(node);
+ }
+ }
+
+ private void rotateLeft(@NonNull Node node) {
+ Node rightChild = node.getRight();
+ Node parent = node.getParent();
+ node.setRight(null);
+ Node rightLeftChild = rightChild == null ? null : rightChild.getLeft();
+ if (parent != null && parent.getRight() == node) {
+ parent.setRight(rightChild);
+ }
+ if (parent != null && parent.getLeft() == node) {
+ parent.setLeft(rightChild);
+ }
+ if (rightChild != null) {
+ rightChild.setLeft(node);
+ rightChild.setParent(node.getParent());
+ }
+ if (node == this.root) {
+ this.root = rightChild;
+ }
+ node.setParent(rightChild);
+ node.setRight(rightLeftChild);
+ if (rightLeftChild != null) {
+ rightLeftChild.setParent(node);
+ }
+ }
+
+ private Node getUncle(@NonNull Node parent) {
+ Node grandParent = parent.getParent();
+ if (grandParent.getLeft() == parent) {
+ return grandParent.getRight();
+ }
+ if (grandParent.getRight() == parent) {
+ return grandParent.getLeft();
+ }
+ throw new IllegalStateException("Parent is not a child of grandparent");
+ }
+
+ @Override
+ public boolean contains(@NonNull T element) {
+ return contains(this.root, element);
+ }
+
+ private boolean contains(Node node, T el) {
+ if (node == null) {
+ return false;
+ }
+ int res = el.compareTo(node.getValue());
+ if (res == 0) {
+ return true;
+ }
+ if (res < 0) {
+ return contains(node.getLeft(), el);
+ }
+ return contains(node.getRight(), el);
+ }
+
+ @Override
+ public int size() {
+ return this.size;
+ }
+
+ @Override
+ public int depth() {
+ return root == null ? 0 : depthHelper(this.root) - 1;
+ }
+
+ int depthHelper(Node node) {
+ if (node == null) {
+ return 0;
+ }
+ return 1 + Math.max(depthHelper(node.getLeft()), depthHelper(node.getRight()));
+ }
+
+ @Override
+ public void inOrderTraversal(Consumer consumer) {
+ inOrderTraversal(root, consumer);
+ }
+
+ private void inOrderTraversal(Node node, Consumer consumer) {
+ if (node == null) {
+ return;
+ }
+ inOrderTraversal(node.getLeft(), consumer);
+ consumer.accept(node.getValue());
+ inOrderTraversal(node.getRight(), consumer);
+ }
+
+ @Override
+ public void preOrderTraversal(Consumer consumer) {
+ preOrderTraversal(root, consumer);
+ }
+
+ private void preOrderTraversal(Node node, Consumer consumer) {
+ if (node == null) {
+ return;
+ }
+ consumer.accept(node.getValue());
+ preOrderTraversal(node.getLeft(), consumer);
+ preOrderTraversal(node.getRight(), consumer);
+ }
+
+ @Data
+ private static class Node {
+ V value;
+ boolean color;
+ @ToString.Exclude
+ Node parent;
+ Node left;
+ Node right;
+
+ private Node(V value, Node parent) {
+ this.value = value;
+ this.parent = parent;
+ this.color = RED;
+ }
+
+ private Node getGrandParent() {
+ if (this.parent == null) {
+ return null;
+ }
+ return this.getParent().getParent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/AbstractTreeUnitTest.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/AbstractTreeUnitTest.java
new file mode 100644
index 00000000..b5cc63c9
--- /dev/null
+++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/AbstractTreeUnitTest.java
@@ -0,0 +1,159 @@
+package com.bobocode.cs;
+
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.TestMethodOrder;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.function.Predicate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public abstract class AbstractTreeUnitTest {
+
+ protected static final Integer[] someElements = {10, 9, 11, 8, 12, 7};
+
+ protected static final Predicate SIZE_FIELD = field ->
+ field.getName().toLowerCase().contains("size") || field.getName().toLowerCase().contains("length");
+
+ protected static final Predicate NODE_FIELD = field ->
+ field.getType().getSimpleName().equals("Node");
+
+ protected static final Predicate ELEMENT_FIELD = field -> field.getName().toLowerCase().contains("value");
+
+ protected static final Predicate LEFT_FIELD = field ->
+ field.getName().toLowerCase().contains("left")
+ && field.getType().getSimpleName().equals("Node");
+
+ protected static final Predicate RIGHT_FIELD = field ->
+ field.getName().toLowerCase().contains("right")
+ && field.getType().getSimpleName().equals("Node");
+
+ protected static Class> getInnerClass(Class> treeClazz) {
+ return Arrays.stream(treeClazz.getDeclaredClasses())
+ .filter(Class::isMemberClass)
+ .findAny()
+ .orElseThrow();
+ }
+
+ @SneakyThrows
+ protected int getInnerSize(Tree> tree) {
+ return (int) getInnerSizeField(tree.getClass()).get(tree);
+ }
+
+ protected Field getInnerSizeField(Class> treeClazz) {
+ Field sizeField = Arrays.stream(treeClazz.getDeclaredFields())
+ .filter(SIZE_FIELD)
+ .findAny()
+ .orElseThrow();
+ sizeField.setAccessible(true);
+ return sizeField;
+ }
+
+ protected void checkTreeNode(Class> treeClazz) {
+ Class> innerClass = getInnerClass(treeClazz);
+ String name = innerClass.getSimpleName();
+
+ assertThat(name).isEqualTo("Node");
+ }
+
+ protected void checkTreeFieldsCheck(Class> treeClazz) {
+ boolean hasSizeField = Arrays.stream(treeClazz.getDeclaredFields())
+ .anyMatch(SIZE_FIELD);
+
+ boolean hasNodeField = Arrays.stream(treeClazz.getDeclaredFields())
+ .anyMatch(NODE_FIELD);
+
+ assertThat(hasSizeField).isTrue();
+ assertThat(hasNodeField).isTrue();
+ }
+
+ protected void commonNodeFieldsCheck(Class> treeClazz) {
+ Class> innerClass = getInnerClass(treeClazz);
+
+ boolean isElement = Arrays.stream(innerClass.getDeclaredFields())
+ .anyMatch(ELEMENT_FIELD);
+
+ boolean isLeft = Arrays.stream(innerClass.getDeclaredFields())
+ .anyMatch(LEFT_FIELD);
+
+ boolean isRight = Arrays.stream(innerClass.getDeclaredFields())
+ .anyMatch(RIGHT_FIELD);
+
+ assertThat(isElement).isTrue();
+ assertThat(isLeft).isTrue();
+ assertThat(isRight).isTrue();
+ }
+
+ protected void testOfMethod(Tree tree) {
+ for (var e : AbstractTreeUnitTest.someElements) {
+ assertThat(contains(getRootObject(tree), e)).isTrue();
+ }
+ assertThat(getInnerSize(tree)).isEqualTo(AbstractTreeUnitTest.someElements.length);
+ }
+
+ protected void testInsertMethod(Tree tree) {
+ for (Integer e : AbstractTreeUnitTest.someElements) {
+ assertThat(contains(getRootObject(tree), e)).isFalse(); //does not contain
+ assertThat(tree.insert(e)).isTrue(); //do insert
+ assertThat(contains(getRootObject(tree), e)).isTrue(); //and contains
+ }
+ }
+
+ protected Object findNodeByElement(Object node, int element) {
+ if (node == null) {
+ return null;
+ }
+ if (element == getElement(node)) {
+ return node;
+ } else if (element < getElement(node)) {
+ return findNodeByElement(getLeftNode(node), element);
+ } else if (element > getElement(node)) {
+ return findNodeByElement(getRightNode(node), element);
+ } else {
+ return node;
+ }
+ }
+
+ @SneakyThrows
+ protected Object getRootObject(Tree> tree) {
+ Field nodeField = Arrays.stream(tree.getClass().getDeclaredFields())
+ .filter(NODE_FIELD)
+ .findAny()
+ .orElseThrow();
+ nodeField.setAccessible(true);
+ return nodeField.get(tree);
+ }
+
+ @SneakyThrows
+ protected boolean contains(Object node, int element) {
+ return findNodeByElement(node, element) != null;
+ }
+
+ @SneakyThrows
+ protected int getElement(Object node) {
+ return (int) getNodesField(node, ELEMENT_FIELD).get(node);
+ }
+
+ @SneakyThrows
+ protected Object getLeftNode(Object node) {
+ return getNodesField(node, LEFT_FIELD).get(node);
+ }
+
+ @SneakyThrows
+ protected Object getRightNode(Object node) {
+ return getNodesField(node, RIGHT_FIELD).get(node);
+ }
+
+ @SneakyThrows
+ protected Field getNodesField(Object node, Predicate option) {
+ Field field = Arrays.stream(node.getClass().getDeclaredFields())
+ .filter(option)
+ .findAny()
+ .orElseThrow();
+ field.setAccessible(true);
+ return field;
+ }
+
+}
diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/test/java/com/bobocode/cs/RecursiveBinarySearchTreeTest.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/binary/BinarySearchTreeTest.java
similarity index 57%
rename from 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/test/java/com/bobocode/cs/RecursiveBinarySearchTreeTest.java
rename to 2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/binary/BinarySearchTreeTest.java
index ac58a0b4..1bd11d21 100644
--- a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/test/java/com/bobocode/cs/RecursiveBinarySearchTreeTest.java
+++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/binary/BinarySearchTreeTest.java
@@ -1,5 +1,7 @@
-package com.bobocode.cs;
+package com.bobocode.cs.binary;
+import com.bobocode.cs.AbstractTreeUnitTest;
+import com.bobocode.cs.Tree;
import lombok.SneakyThrows;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
@@ -8,13 +10,11 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
@@ -30,99 +30,47 @@
* @author Maksym Stasiuk
*/
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
-class RecursiveBinarySearchTreeTest {
- private static final Predicate SIZE_FIELD = field ->
- field.getName().toLowerCase().contains("size") || field.getName().toLowerCase().contains("length");
-
- private static final Predicate NODE_FIELD = field ->
- field.getType().getSimpleName().equals("Node");
-
- private static final Predicate ELEMENT_FIELD = field ->
- field.getName().toLowerCase().contains("element")
- || field.getName().toLowerCase().contains("item")
- || field.getName().toLowerCase().contains("value");
-
- private static final Predicate LEFT_FIELD = field ->
- field.getName().toLowerCase().contains("left")
- && field.getType().getSimpleName().equals("Node");
-
- private static final Predicate RIGHT_FIELD = field ->
- field.getName().toLowerCase().contains("right")
- && field.getType().getSimpleName().equals("Node");
-
- private static final Integer[] someElements = {10, 9, 11, 8, 12, 7};
-
- private BinarySearchTree tree = new RecursiveBinarySearchTree<>();
+class BinarySearchTreeTest extends AbstractTreeUnitTest {
+
+ private Tree tree = new BinarySearchTree<>();
@Test
@Order(1)
void properNodeClassNameCheck() {
- Class> innerClass = getInnerClass();
- String name = innerClass.getSimpleName();
-
- assertThat(name).isEqualTo("Node");
+ checkTreeNode(BinarySearchTree.class);
}
@Test
@Order(2)
void properTreeFieldsCheck() {
- Class> treeClass = tree.getClass();
-
- boolean hasSizeField = Arrays.stream(treeClass.getDeclaredFields())
- .anyMatch(SIZE_FIELD);
-
- boolean hasNodeField = Arrays.stream(treeClass.getDeclaredFields())
- .anyMatch(NODE_FIELD);
-
- assertThat(hasSizeField).isTrue();
- assertThat(hasNodeField).isTrue();
+ checkTreeFieldsCheck(BinarySearchTree.class);
}
@Test
@Order(3)
void properNodeFieldsCheck() {
- Class> innerClass = getInnerClass();
-
- boolean isElement = Arrays.stream(innerClass.getDeclaredFields())
- .anyMatch(ELEMENT_FIELD);
-
- boolean isLeft = Arrays.stream(innerClass.getDeclaredFields())
- .anyMatch(LEFT_FIELD);
-
- boolean isRight = Arrays.stream(innerClass.getDeclaredFields())
- .anyMatch(RIGHT_FIELD);
-
- assertThat(isElement).isTrue();
- assertThat(isLeft).isTrue();
- assertThat(isRight).isTrue();
+ commonNodeFieldsCheck(BinarySearchTree.class);
}
@Test
@Order(4)
void of() {
- tree = RecursiveBinarySearchTree.of(someElements);
- for (var e : someElements) {
- assertThat(contains(getRootObject(), e)).isTrue();
- }
- assertThat(getInnerSize()).isEqualTo(someElements.length);
+ tree = BinarySearchTree.of(someElements);
+ testOfMethod(tree);
}
@Test
@Order(5)
void insert() {
- for (Integer e : someElements) {
- assertThat(contains(getRootObject(), e)).isFalse(); //does not contain
- assertThat(tree.insert(e)).isTrue(); //do insert
- assertThat(contains(getRootObject(), e)).isTrue(); //and contains
- }
+ testInsertMethod(tree);
}
@Test
@Order(6)
void insertToRootIfTreeIsEmpty() {
- assertThat(getRootObject()).isNull();
+ assertThat(getRootObject(tree)).isNull();
tree.insert(10);
- assertThat(getRootObject()).isNotNull();
+ assertThat(getRootObject(tree)).isNotNull();
}
@Test
@@ -131,7 +79,7 @@ void insertLeftIfLessThanRoot() {
tree.insert(10); //root
tree.insert(5); //left
- assertThat(getLeftNode(getRootObject())).isNotNull();
+ assertThat(getLeftNode(getRootObject(tree))).isNotNull();
}
@Test
@@ -140,7 +88,7 @@ void insertRightIfGreaterThanRoot() {
tree.insert(10); //root
tree.insert(15); //right
- assertThat(getRightNode(getRootObject())).isNotNull();
+ assertThat(getRightNode(getRootObject(tree))).isNotNull();
}
@Test
@@ -192,7 +140,7 @@ void sizeIsGrowingWhenInserting() {
tree.insert(15);
tree.insert(20);
- assertThat(getInnerSize()).isEqualTo(3);
+ assertThat(getInnerSize(tree)).isEqualTo(3);
}
@Test
@@ -200,13 +148,13 @@ void sizeIsGrowingWhenInserting() {
void sizeDoesNotGrowWhenInsertingNotUnique() {
fillTestTree(10, 11, 12);
- assertThat(getInnerSize()).isEqualTo(3);
+ assertThat(getInnerSize(tree)).isEqualTo(3);
tree.insert(10);
tree.insert(11);
tree.insert(12);
- assertThat(getInnerSize()).isEqualTo(3);
+ assertThat(getInnerSize(tree)).isEqualTo(3);
}
@Order(16)
@@ -243,12 +191,24 @@ void inorderTraversal() {
Integer[] sortedElements = Arrays.copyOf(someElements, someElements.length);
Arrays.sort(sortedElements);
- List traversedElements = new ArrayList<>(getInnerSize());
+ List traversedElements = new ArrayList<>(getInnerSize(tree));
tree.inOrderTraversal(traversedElements::add);
assertThat(traversedElements).isEqualTo(List.of(sortedElements));
}
+ @Test
+ @Order(20)
+ void preOrderTraversal() {
+ fillTestTree(someElements);
+ Integer[] preOrderOfSomeElements = {10, 9, 8, 7, 11, 12};
+
+ List traversedElements = new ArrayList<>(getInnerSize(tree));
+ tree.preOrderTraversal(traversedElements::add);
+
+ assertThat(traversedElements).isEqualTo(List.of(preOrderOfSomeElements));
+ }
+
public static Stream depthArguments() {
return Stream.of(
//empty tree
@@ -289,28 +249,6 @@ public static Stream depthArguments() {
arguments(new Integer[]{6, 2, 7, 1, 5, 8, 4, 9, 3}, 4));
}
-
- @SneakyThrows
- private int getInnerSize() {
- return (int) getInnerSizeField().get(tree);
- }
-
- private Field getInnerSizeField() {
- Field sizeField = Arrays.stream(tree.getClass().getDeclaredFields())
- .filter(SIZE_FIELD)
- .findAny()
- .orElseThrow();
- sizeField.setAccessible(true);
- return sizeField;
- }
-
- private Class> getInnerClass() {
- return Arrays.stream(tree.getClass().getDeclaredClasses())
- .filter(Class::isMemberClass)
- .findAny()
- .orElseThrow();
- }
-
@SneakyThrows
private Field getRootField() {
Field nodeField = Arrays.stream(tree.getClass().getDeclaredFields())
@@ -321,65 +259,13 @@ private Field getRootField() {
return nodeField;
}
- @SneakyThrows
- private Object getRootObject() {
- Field nodeField = Arrays.stream(tree.getClass().getDeclaredFields())
- .filter(NODE_FIELD)
- .findAny()
- .orElseThrow();
- nodeField.setAccessible(true);
- return nodeField.get(tree);
- }
- @SneakyThrows
- private boolean contains(Object node, int element) {
- return findNodeByElement(node, element) != null;
- }
- private Object findNodeByElement(Object node, int element) {
- if (node == null) {
- return null;
- }
- if (element == getElement(node)) {
- return node;
- } else if (element < getElement(node)) {
- return findNodeByElement(getLeftNode(node), element);
- } else if (element > getElement(node)) {
- return findNodeByElement(getRightNode(node), element);
- } else {
- return node;
- }
- }
-
- @SneakyThrows
- private Field getNodesField(Object node, Predicate option) {
- Field field = Arrays.stream(node.getClass().getDeclaredFields())
- .filter(option)
- .findAny()
- .orElseThrow();
- field.setAccessible(true);
- return field;
- }
-
- @SneakyThrows
- private int getElement(Object node) {
- return (int) getNodesField(node, ELEMENT_FIELD).get(node);
- }
-
- @SneakyThrows
- private Object getLeftNode(Object node) {
- return getNodesField(node, LEFT_FIELD).get(node);
- }
-
- @SneakyThrows
- private Object getRightNode(Object node) {
- return getNodesField(node, RIGHT_FIELD).get(node);
- }
@SneakyThrows
private Object newNode(int element) {
Object nodeInstance;
- Constructor>[] constructors = getInnerClass().getDeclaredConstructors();
+ Constructor>[] constructors = getInnerClass(BinarySearchTree.class).getDeclaredConstructors();
Constructor> constructor;
constructor = constructors[0];
@@ -395,9 +281,9 @@ private Object newNode(int element) {
}
@SneakyThrows
- private void insertElement(int element) {
+ private void insertElement(Tree tree, int element) {
- Object root = getRootObject();
+ Object root = getRootObject(tree);
if (root == null) {
getRootField().set(tree, newNode(element));
@@ -438,10 +324,10 @@ private boolean insertRight(Object node, int element) {
@SneakyThrows
private void fillTestTree(Integer... elements) {
- tree = new RecursiveBinarySearchTree<>();
+ tree = new BinarySearchTree<>();
for (Integer e : elements) {
- insertElement(e);
+ insertElement(tree, e);
}
- getInnerSizeField().set(tree, elements.length);
+ getInnerSizeField(tree.getClass()).set(tree, elements.length);
}
}
diff --git a/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/redblack/RedBlackTreeTest.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/redblack/RedBlackTreeTest.java
new file mode 100644
index 00000000..c013841e
--- /dev/null
+++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/test/java/com/bobocode/cs/redblack/RedBlackTreeTest.java
@@ -0,0 +1,570 @@
+package com.bobocode.cs.redblack;
+
+import com.bobocode.cs.AbstractTreeUnitTest;
+import com.bobocode.cs.Tree;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.ClassOrderer.OrderAnnotation;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import static java.lang.reflect.Modifier.isStatic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNullPointerException;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+@TestClassOrder(OrderAnnotation.class)
+@DisplayName("Red Black Tree Test")
+public class RedBlackTreeTest extends AbstractTreeUnitTest {
+
+ private final Predicate COLOR_FIELD = field ->
+ field.getName().toLowerCase().contains("color")
+ && field.getType().getSimpleName().equals("boolean");
+
+ private final Predicate PARENT_FIELD = field ->
+ field.getName().toLowerCase().contains("parent")
+ && field.getType().getSimpleName().equals("Node");
+ private final Predicate VALUE_FIELD = field -> field.getName().toLowerCase().contains("value");
+
+ @Nested
+ @Order(1)
+ @DisplayName("1. Node Test")
+ @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+ class NodeClassTest {
+
+ private static final String NODE_PATH = "com.bobocode.cs.redblack.RedBlackSearchTree$Node";
+
+ @Test
+ @Order(1)
+ @DisplayName("A nested class Node exists")
+ void nodeClassExists() {
+ checkTreeNode(RedBlackSearchTree.class);
+ }
+
+ @Test
+ @Order(2)
+ @DisplayName("Node is a static nested class")
+ @SneakyThrows
+ void nodeIsStaticClass() {
+ var nodeClass = Class.forName(NODE_PATH);
+ assertTrue(isStatic(nodeClass.getModifiers()));
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("Node class has 'element', 'left' and 'right' fields")
+ void nodeHasDefaultTreeElements() {
+ commonNodeFieldsCheck(RedBlackSearchTree.class);
+ }
+
+
+ @Test
+ @Order(4)
+ @DisplayName("Node class has 'parent'")
+ void nodeHasParentAndColorField() {
+ Class> innerClass = getInnerClass(RedBlackSearchTree.class);
+ boolean parentFieldPresent = Arrays.stream(innerClass.getDeclaredFields()).anyMatch(PARENT_FIELD);
+ Assertions.assertTrue(parentFieldPresent);
+ }
+
+ @Test
+ @Order(5)
+ @DisplayName("Node class's 'color' is boolean")
+ void nodeColorFieldIsBoolean() {
+ Class> innerClass = getInnerClass(RedBlackSearchTree.class);
+ boolean colorFieldPresent = Arrays.stream(innerClass.getDeclaredFields()).anyMatch(COLOR_FIELD);
+ Assertions.assertTrue(colorFieldPresent);
+ }
+ }
+
+ @Nested
+ @Order(2)
+ @DisplayName("2. Tree Class Test")
+ @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+ class TreeClassTest {
+ private static final Predicate ROTATE_RIGHT_METHOD_CHECK = method ->
+ method.getName().equalsIgnoreCase("rotateRight");
+ private static final Predicate ROTATE_LEFT_METHOD_CHECK = method ->
+ method.getName().equalsIgnoreCase("rotateLeft");
+ private static final Predicate REPAIR_METHOD_CHECK = method ->
+ method.getName().equalsIgnoreCase("repair");
+ private static final BiPredicate RED_BLACK_COLOR_FIELD_EXISTS = (color, field) -> {
+ try {
+ field.setAccessible(true);
+ boolean booleanColor = field.getBoolean(null);
+ // red static field is true
+ if (color.equalsIgnoreCase("red") && !booleanColor) {
+ return false;
+ }
+ // black static field is false
+ if (color.equalsIgnoreCase("black") && booleanColor) {
+ return false;
+ }
+ return field.getName().equalsIgnoreCase(color) && Modifier.isStatic(field.getModifiers());
+ } catch (IllegalAccessException e) {
+ return false;
+ }
+ };
+
+
+ @Test
+ @Order(1)
+ @DisplayName("Size and Node field")
+ void properTreeFieldsCheck() {
+ checkTreeFieldsCheck(RedBlackSearchTree.class);
+ }
+
+ @Test
+ @Order(2)
+ @DisplayName("RotateRight method exists")
+ void treeHasRotateRightMethod() {
+ boolean containsRightMethod = Arrays.stream(RedBlackSearchTree.class.getDeclaredMethods())
+ .anyMatch(ROTATE_RIGHT_METHOD_CHECK);
+ Assertions.assertTrue(containsRightMethod);
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("RotateLeft method exists")
+ void treeHasRotateLetMethod() {
+ boolean containsLeftMethod = Arrays.stream(RedBlackSearchTree.class.getDeclaredMethods())
+ .anyMatch(ROTATE_LEFT_METHOD_CHECK);
+ Assertions.assertTrue(containsLeftMethod);
+ }
+
+ @Test
+ @Order(4)
+ @DisplayName("Repair method exists")
+ void treeHasRepairMethod() {
+ boolean repairMethod = Arrays.stream(RedBlackSearchTree.class.getDeclaredMethods())
+ .anyMatch(REPAIR_METHOD_CHECK);
+ Assertions.assertTrue(repairMethod);
+ }
+
+ @Test
+ @Order(5)
+ @DisplayName("Tree has static Red And Black field")
+ void treeHasPrivateStaticREDTrueAndBlackFalseField() {
+ boolean redColorFieldExists = Arrays.stream(RedBlackSearchTree.class.getDeclaredFields())
+ .filter(field -> field.getName().equalsIgnoreCase("red"))
+ .anyMatch(field -> RED_BLACK_COLOR_FIELD_EXISTS.test("red", field));
+ Assertions.assertTrue(redColorFieldExists);
+
+ boolean blackColorFieldExists = Arrays.stream(RedBlackSearchTree.class.getDeclaredFields())
+ .filter(field -> field.getName().equalsIgnoreCase("black"))
+ .anyMatch(field -> RED_BLACK_COLOR_FIELD_EXISTS.test("black", field));
+ Assertions.assertTrue(blackColorFieldExists);
+ }
+ }
+
+ @Nested
+ @Order(3)
+ @DisplayName("3.Test Insert Contains And Depth Function")
+ @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+ class InsertContainsDepthFunctionsTest {
+
+ private static final boolean RED = true;
+ private static final boolean BLACK = false;
+
+ private static Stream expectedTreeResults() {
+ return Stream.of(
+ //tree with a single element black element
+ /*
+ * .....50(B)
+ */
+ arguments(
+ new Integer[]{50}, // values to insert
+ new Integer[]{50}, // expected pre-order traversal
+ new Integer[]{}, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK}, // expected color inorder traversal
+ new Boolean[]{BLACK}, // expected color pre-order traversal
+ 0
+ ),
+ /*
+ * .......50(B)
+ * ....../
+ * ...35(R)
+ */
+ arguments(
+ new Integer[]{50, 35}, // values to insert
+ new Integer[]{50, 35}, // expected pre-order traversal
+ new Integer[]{ 50}, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED}, // expected color pre-order traversal
+ 1 // depth
+ ),
+ /*
+ * .......35(B)
+ * ....../ \
+ * ...10(R) 50(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10}, // values to insert
+ new Integer[]{35, 10, 50}, // expected pre-order traversal
+ new Integer[]{ 35, 35}, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, RED}, // expected color pre-order traversal
+ 1 // depth
+ ),
+ /* repair case 1 left uncle is red -> recolor parent grandparent and uncle
+ * .......35(B)
+ * ....../ \
+ * ...10(B) 50(B)
+ * ..........\
+ * .........120(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 120}, // values to insert
+ new Integer[]{35, 10, 50, 120}, // expected pre-order traversal
+ new Integer[]{ 35, 35, 50}, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, BLACK, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, BLACK, BLACK, RED}, // expected color pre-order traversal
+ 2// depth
+ ),
+
+ /* repair case 1 right uncle is red -> recolor parent grandparent and uncle
+ * .......35(B)
+ * ....../ \
+ * ...10(B) 50(B)
+ * .../
+ * 5(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5}, // values to insert
+ new Integer[]{35, 10, 5, 50 }, // expected pre-order traversal
+ new Integer[]{ 35, 10, 35}, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK, BLACK, BLACK}, // expected color inorder traversal
+ new Boolean[]{BLACK, BLACK, RED, BLACK}, // expected color pre-order traversal
+ 2 // depth
+ ),
+ /* repair case 2 right triangle is formed and uncle is black -> rotate parent
+ * .......35(B)
+ * ....../ \
+ * ... 8(B) 50(B)
+ * .../ \
+ * 5(R) 10(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8}, // values to insert
+ new Integer[]{35, 8, 5, 10, 50}, // expected pre-order traversal
+ new Integer[]{ 35, 8, 8, 35 }, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK, RED, BLACK, BLACK}, // expected color inorder traversal
+ new Boolean[]{BLACK, BLACK, RED, RED, BLACK}, // expected color pre-order traversal
+ 2 // depth
+ ),
+ /* repair case 3 flat line is formed to right and uncle is black -> rotate parent and recolor
+ * ............35(B)
+ * ......... / \
+ * ...... 8(B) 120(B)
+ * ..... / \ / \
+ * ...5(R) 10(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200}, // values to insert
+ new Integer[]{35, 8, 5, 10, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 35, 8, 8, 35, 120, 120}, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK, RED, BLACK, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, BLACK, RED, RED, BLACK, RED, RED}, // expected color pre-order traversal
+ 2 // depth
+ ),
+ /* repair case 1 uncle is red -> recolor parent, grandparent and uncle
+ * ............35(B)
+ * ......... / \
+ * ...... 8(R) 120(B)
+ * ..... / \ / \
+ * ...5(B) 10(B) 50(R) 200(R)
+ * ../
+ * 3(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3}, // values to insert
+ new Integer[]{35, 8, 5, 3, 10, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 35, 8, 5, 8, 35, 120, 120 }, // expected parent nodes pre-order traversal
+
+ new Boolean[]{RED, BLACK, RED, BLACK, BLACK, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /* nothing to repair
+ * ............35(B)
+ * ......... / \
+ * ...... 8(R) 120(B)
+ * ..... / \ / \
+ * ...5(B) 10(B) 50(R) 200(R)
+ * ../ \
+ * 3(R) 6(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6}, // values to insert
+ new Integer[]{35, 8, 5, 3, 6, 10, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 35, 8, 5, 5, 8, 35, 120, 120 }, // expected parent nodes pre-order traversal
+ new Boolean[]{RED, BLACK, RED, RED, BLACK, BLACK, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, RED, BLACK, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+
+ /*
+ * ............8(B)
+ * ......... / \
+ * ...... 5(R) 35(R)
+ * ..... / \ / \
+ * ...3(B) 6(B) 10(B) 120(B)
+ * \ / \
+ * 7(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 10, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 35, 120, 120}, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, BLACK, RED, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, BLACK, RED, RED, BLACK, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /*
+ * ............8(B)
+ * ......... / \
+ * ...... 5(R) 35(R)
+ * ..... / \ / \
+ * ...3(B) 6(B) 10(B) 120(B)
+ * \ / \
+ * 7(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 10, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 35, 120, 120}, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, BLACK, RED, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, BLACK, RED, RED, BLACK, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /*
+ * ............ 8(B)
+ * ......... / \
+ * ...... 5(R) 35(R)
+ * ..... / \ / \
+ * ...3(B) 6(B) 10(B) 120(B)
+ * \ \ / \
+ * 7(R) 20(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7, 20}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 10, 20, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 10, 35, 120, 120}, // expected parent nodes pre-order traversal
+
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, BLACK, RED, RED, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, BLACK, RED, RED, BLACK, RED, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /*
+ * ............ 8(B)
+ * ......... / \
+ * ...... 5(R) 35(R)
+ * ..... / \ / \
+ * ...3(B) 6(B) 10(B) 120(B)
+ * \ \ / \
+ * 7(R) 20(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7, 20}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 10, 20, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 10, 35, 120, 120}, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, BLACK, RED, RED, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, BLACK, RED, RED, BLACK, RED, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /*
+ * ..............8(B)
+ * .........../ \
+ * ...... 5(R) 35(R)
+ * .... / \ / \
+ * ..3(B) 6(B) 20(B) 120(B)
+ * \ / \ / \
+ *......... 7(R) 10(R) 30(R) 50(R) 200(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7, 20, 30}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 20, 10, 30, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 20, 20, 35, 120, 120 }, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, RED, BLACK, RED, BLACK, RED, BLACK, RED, RED, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, RED, BLACK, BLACK, RED, RED, BLACK, RED, RED, BLACK, RED, RED}, // expected color pre-order traversal
+ 3 // depth
+ ),
+ /*
+ * ..............8(B)
+ * .........../ \
+ * ...... 5(B) 35(B)
+ * .... / \ / \
+ * ..3(B) 6(B) 20(R) 120(B)
+ * \ / \ / \
+ *......... 7(R) 10(B) 30(B) 50(R) 200(R)
+ * ......................\
+ * .....................33(R)
+ */
+ arguments(
+ new Integer[]{50, 35, 10, 5, 8, 120, 200, 3, 6, 7, 20, 30, 33}, // values to insert
+ new Integer[]{8, 5, 3, 6, 7, 35, 20, 10, 30, 33, 120, 50, 200}, // expected pre-order traversal
+ new Integer[]{ 8, 5, 5, 6, 8, 35, 20, 20, 30, 35, 120, 120 }, // expected parent nodes pre-order traversal
+ new Boolean[]{BLACK, BLACK, BLACK, RED, BLACK, BLACK, RED, BLACK, RED, BLACK, RED, BLACK, RED}, // expected color inorder traversal
+ new Boolean[]{BLACK, BLACK, BLACK, BLACK, RED, BLACK, RED, BLACK, BLACK, RED, BLACK, RED, RED}, // expected color pre-order traversal
+ 4 // depth
+ )
+ );
+ }
+
+ @Test
+ @Order(1)
+ @DisplayName("Null insert is not allowed")
+ void insertThrowsExceptionWhenArgumentIsNull() {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(someElements);
+ assertThatNullPointerException().isThrownBy(() -> redBlackSearchTree.insert(null));
+ }
+
+ @Test
+ @Order(2)
+ @DisplayName("Null contains is not allowed")
+ void containsThrowsExceptionWhenArgumentIsNull() {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(someElements);
+ assertThatNullPointerException().isThrownBy(() -> redBlackSearchTree.contains(null));
+ }
+
+ @Test
+ @Order(3)
+ void containsReturnsTrueIfElementExist() {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(someElements);
+ assertThat(redBlackSearchTree.contains(10)).isTrue();
+ assertThat(redBlackSearchTree.contains(9)).isTrue();
+ assertThat(redBlackSearchTree.contains(11)).isTrue();
+ }
+
+ @Test
+ @Order(4)
+ @DisplayName("Of function works")
+ void of() {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(someElements);
+ testOfMethod(redBlackSearchTree);
+ }
+
+ @Test
+ @Order(5)
+ @DisplayName("Insert function works")
+ void insert() {
+ final Tree redBlackSearchTree = new RedBlackSearchTree<>();
+ testInsertMethod(redBlackSearchTree);
+ }
+
+ @Test
+ @Order(6)
+ @DisplayName("Only unique values are allowed")
+ void sizeDoesNotGrowWhenInsertingNotUnique() {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(10, 11, 12);
+
+
+ assertThat(getInnerSize(redBlackSearchTree)).isEqualTo(3);
+
+ redBlackSearchTree.insert(10);
+ redBlackSearchTree.insert(11);
+ redBlackSearchTree.insert(12);
+
+ assertThat(getInnerSize(redBlackSearchTree)).isEqualTo(3);
+ }
+
+ @ParameterizedTest
+ @Order(7)
+ @DisplayName("Insert method re-balances tree correctly")
+ @MethodSource("expectedTreeResults")
+ void testBalancingInInsertMethod(Integer[] valuesToInsert,
+ Integer[] expectedPreOrderValueSequence,
+ Integer[] expectedParentPreOrderValueSequence,
+ Boolean[] expectedInorderColorSequence,
+ Boolean[] expectedPreorderColorSequence,
+ Integer expectedDepth
+ ) {
+ final Tree redBlackSearchTree = RedBlackSearchTree.of(valuesToInsert);
+
+
+ Integer[] expectedInorderValueSequence = Arrays.copyOf(valuesToInsert, valuesToInsert.length);
+ Arrays.sort(expectedInorderValueSequence);
+
+ List inOrderTraversedElements = new ArrayList<>(getInnerSize(redBlackSearchTree));
+ redBlackSearchTree.inOrderTraversal(inOrderTraversedElements::add);
+
+
+ List preOrderTraversedElements = new ArrayList<>(getInnerSize(redBlackSearchTree));
+ redBlackSearchTree.preOrderTraversal(preOrderTraversedElements::add);
+
+ List preOrderParentTraversedElements = new ArrayList<>(getInnerSize(redBlackSearchTree));
+ preorderTraverseParentNode(getRootObject(redBlackSearchTree), parentValue -> preOrderParentTraversedElements.add((Integer) parentValue), PARENT_FIELD, VALUE_FIELD);
+
+
+ List inOrderTraversedColor = new ArrayList<>(getInnerSize(redBlackSearchTree));
+ inorderTraverseTree(getRootObject(redBlackSearchTree), color -> inOrderTraversedColor.add((Boolean) color), COLOR_FIELD);
+
+ List preOrderTraversedColor = new ArrayList<>(getInnerSize(redBlackSearchTree));
+ preorderTraverseTree(getRootObject(redBlackSearchTree), color -> preOrderTraversedColor.add((Boolean) color), COLOR_FIELD);
+ // size and depth
+ assertEquals(redBlackSearchTree.depth(), expectedDepth);
+ assertEquals(redBlackSearchTree.size(), valuesToInsert.length);
+ // elements
+ assertThat(inOrderTraversedElements).isEqualTo(List.of(expectedInorderValueSequence));
+ assertThat(preOrderTraversedElements).isEqualTo(List.of(expectedPreOrderValueSequence));
+ // colors
+ assertThat(inOrderTraversedColor).isEqualTo(List.of(expectedInorderColorSequence));
+ assertThat(preOrderTraversedColor).isEqualTo(List.of(expectedPreorderColorSequence));
+ // parent values
+ assertThat(preOrderParentTraversedElements).isEqualTo(List.of(expectedParentPreOrderValueSequence));
+ }
+
+
+ @SneakyThrows
+ @SuppressWarnings("unchecked")
+ private void inorderTraverseTree(Object node, Consumer consumer, Predicate fieldPredicate) {
+ if (node == null) {
+ return;
+ }
+ inorderTraverseTree(getLeftNode(node), consumer, fieldPredicate);
+ consumer.accept((R) getNodesField(node, fieldPredicate).get(node));
+ inorderTraverseTree(getRightNode(node), consumer, fieldPredicate);
+ }
+
+ @SneakyThrows
+ @SuppressWarnings("unchecked")
+ private void preorderTraverseTree(Object node, Consumer consumer, Predicate fieldPredicate) {
+ if (node == null) {
+ return;
+ }
+ R element = (R) getNodesField(node, fieldPredicate).get(node);
+ if (element != null){
+ consumer.accept(element);
+ }
+ preorderTraverseTree(getLeftNode(node), consumer, fieldPredicate);
+ preorderTraverseTree(getRightNode(node), consumer, fieldPredicate);
+ }
+
+ @SneakyThrows
+ @SuppressWarnings("unchecked")
+ private void preorderTraverseParentNode(Object node, Consumer consumer, Predicate parentNodePredicate, Predicate valueNodePredicate) {
+ if (node == null) {
+ return;
+ }
+ Object parentNode = getNodesField(node, parentNodePredicate).get(node);
+ if (parentNode != null){
+ consumer.accept((R) getNodesField(parentNode, valueNodePredicate).get(parentNode));
+ }
+ preorderTraverseParentNode(getLeftNode(node), consumer, parentNodePredicate, valueNodePredicate);
+ preorderTraverseParentNode(getRightNode(node), consumer, parentNodePredicate, valueNodePredicate);
+ }
+ }
+}
\ No newline at end of file
diff --git a/2-0-data-structures-and-algorithms/pom.xml b/2-0-data-structures-and-algorithms/pom.xml
index a08d31d2..abe0354d 100644
--- a/2-0-data-structures-and-algorithms/pom.xml
+++ b/2-0-data-structures-and-algorithms/pom.xml
@@ -10,7 +10,7 @@
2-2-3-linked-queue
2-2-4-linked-list
2-2-5-array-list
- 2-2-6-binary-search-tree
+ 2-2-6-trees
2-2-9-hash-table
data-structures-and-algorithms-util