diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/README.md b/2-0-data-structures-and-algorithms/2-2-6-trees/README.md similarity index 100% rename from 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/README.md rename to 2-0-data-structures-and-algorithms/2-2-6-trees/README.md diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/pom.xml b/2-0-data-structures-and-algorithms/2-2-6-trees/pom.xml similarity index 88% rename from 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/pom.xml rename to 2-0-data-structures-and-algorithms/2-2-6-trees/pom.xml index ea860815..72ff1a60 100644 --- a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/pom.xml +++ b/2-0-data-structures-and-algorithms/2-2-6-trees/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - 2-2-6-binary-search-tree + 2-2-6-trees \ No newline at end of file diff --git a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/BinarySearchTree.java b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/Tree.java similarity index 88% rename from 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/BinarySearchTree.java rename to 2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/Tree.java index d7462484..71844847 100644 --- a/2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/BinarySearchTree.java +++ b/2-0-data-structures-and-algorithms/2-2-6-trees/src/main/java/com/bobocode/cs/Tree.java @@ -2,7 +2,7 @@ import java.util.function.Consumer; -public interface BinarySearchTree> { +public interface Tree> { /** * insert an element * @return true if element did not exist in the tree and was inserted successfully @@ -29,4 +29,6 @@ public interface BinarySearchTree> { * @param consumer accepts ref. to node during traversing */ void inOrderTraversal(Consumer consumer); + + void preOrderTraversal(Consumer 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