Question: Develop classes SETint and SETdouble for maintaining ordered sets of keys of primitive int and double types, respectively. (Eliminate code involving values in your solution to Exercise 3.5.5.)

Answer:
package chapter3.section5;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 05/08/17.
 */
public class Exercise7 {

    private class SETint {
        private static final boolean RED = true;
        private static final boolean BLACK = false;

        public final static int EMPTY_KEY = Integer.MIN_VALUE;

        private class Node {
            int key;
            Node left, right;

            boolean color;
            int size;

            Node(int key, int size, boolean color) {
                this.key = key;

                this.size = size;
                this.color = color;
            }
        }

        private Node root;

        public int size() {
            return size(root);
        }

        private int size(Node node) {
            if (node == null) {
                return 0;
            }

            return node.size;
        }

        public boolean isEmpty() {
            return size(root) == 0;
        }

        private boolean isRed(Node node) {
            if (node == null) {
                return false;
            }

            return node.color == RED;
        }

        private Node rotateLeft(Node node) {
            if (node == null || node.right == null) {
                return node;
            }

            Node newRoot = node.right;

            node.right = newRoot.left;
            newRoot.left = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private Node rotateRight(Node node) {
            if (node == null || node.left == null) {
                return node;
            }

            Node newRoot = node.left;

            node.left = newRoot.right;
            newRoot.right = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private void flipColors(Node node) {
            if (node == null || node.left == null || node.right == null) {
                return;
            }

            // The root must have opposite color of its two children
            if ((isRed(node) && !isRed(node.left) && !isRed(node.right))
                    || (!isRed(node) && isRed(node.left) && isRed(node.right))) {
                node.color = !node.color;
                node.left.color = !node.left.color;
                node.right.color = !node.right.color;
            }
        }

        public void add(int key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            root = add(root, key);
            root.color = BLACK;
        }

        private Node add(Node node, int key) {
            if (node == null) {
                return new Node(key, 1, RED);
            }

            if (key < node.key) {
                node.left = add(node.left, key);
            } else if (key > node.key) {
                node.right = add(node.right, key);
            } else {
                node.key = key;
            }

            if (isRed(node.right) && !isRed(node.left)) {
                node = rotateLeft(node);
            }
            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }
            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        public boolean contains(int key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            Node currentNode = root;
            while (currentNode != null) {
                if (key < currentNode.key) {
                    currentNode = currentNode.left;
                } else if (key > currentNode.key) {
                    currentNode = currentNode.right;
                } else {
                    return true;
                }
            }

            return false;
        }

        public int min() {
            if (root == null) {
                throw new NoSuchElementException("Empty set");
            }

            return min(root).key;
        }

        private Node min(Node node) {
            if (node.left == null) {
                return node;
            }

            return min(node.left);
        }

        public int max() {
            if (root == null) {
                throw new NoSuchElementException("Empty set");
            }

            return max(root).key;
        }

        private Node max(Node node) {
            if (node.right == null) {
                return node;
            }

            return max(node.right);
        }

        // Returns the highest key in the set smaller than or equal to key.
        public int floor(int key) {
            Node node = floor(root, key);
            if (node == null) {
                return EMPTY_KEY;
            }

            return node.key;
        }

        private Node floor(Node node, int key) {
            if (node == null) {
                return null;
            }

            if (key == node.key) {
                return node;
            } else if (key < node.key) {
                return floor(node.left, key);
            } else {
                Node rightNode = floor(node.right, key);
                if (rightNode != null) {
                    return rightNode;
                } else {
                    return node;
                }
            }
        }

        // Returns the smallest key in the set greater than or equal to key.
        public int ceiling(int key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return EMPTY_KEY;
            }

            return node.key;
        }

        private Node ceiling(Node node, int key) {
            if (node == null) {
                return null;
            }

            if (key == node.key) {
                return node;
            } else if (key > node.key) {
                return ceiling(node.right, key);
            } else {
                Node leftNode = ceiling(node.left, key);
                if (leftNode != null) {
                    return leftNode;
                } else {
                    return node;
                }
            }
        }

        public int select(int index) {
            if (index >= size()) {
                throw new IllegalArgumentException("Index is higher than set size");
            }

            return select(root, index).key;
        }

        private Node select(Node node, int index) {
            int leftSubtreeSize = size(node.left);

            if (leftSubtreeSize == index) {
                return node;
            } else if (leftSubtreeSize > index) {
                return select(node.left, index);
            } else {
                return select(node.right, index - leftSubtreeSize - 1);
            }
        }

        public int rank(int key) {
            return rank(root, key);
        }

        private int rank(Node node, int key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            if (key < node.key) {
                return rank(node.left, key);
            } else if (key > node.key) {
                return size(node.left) + 1 + rank(node.right, key);
            } else {
                return size(node.left);
            }
        }

        public void deleteMin() {
            if (isEmpty()) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = deleteMin(root);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node deleteMin(Node node) {
            if (node.left == null) {
                return null;
            }

            if (!isRed(node.left) && !isRed(node.left.left)) {
                node = moveRedLeft(node);
            }

            node.left = deleteMin(node.left);
            return balance(node);
        }

        public void deleteMax() {
            if (isEmpty()) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = deleteMax(root);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node deleteMax(Node node) {
            if (isRed(node.left)) {
                node = rotateRight(node);
            }

            if (node.right == null) {
                return null;
            }

            if (!isRed(node.right) && !isRed(node.right.left)) {
                node = moveRedRight(node);
            }

            node.right = deleteMax(node.right);
            return balance(node);
        }

        public void delete(int key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            if (isEmpty() || !contains(key)) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = delete(root, key);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node delete(Node node, int key) {
            if (node == null) {
                return null;
            }

            if (key < node.key) {
                if (!isRed(node.left) && node.left != null && !isRed(node.left.left)) {
                    node = moveRedLeft(node);
                }

                node.left = delete(node.left, key);
            } else {
                if (isRed(node.left)) {
                    node = rotateRight(node);
                }

                if (key == node.key && node.right == null) {
                    return null;
                }

                if (!isRed(node.right) && node.right != null && !isRed(node.right.left)) {
                    node = moveRedRight(node);
                }

                if (key == node.key) {
                    Node aux = min(node.right);
                    node.key = aux.key;
                    node.right = deleteMin(node.right);
                } else {
                    node.right = delete(node.right, key);
                }
            }

            return balance(node);
        }

        private Node moveRedLeft(Node node) {
            // Assuming that node is red and both node.left and node.left.left are black,
            // make node.left or one of its children red
            flipColors(node);

            if (node.right != null && isRed(node.right.left)) {
                node.right = rotateRight(node.right);
                node = rotateLeft(node);
                flipColors(node);
            }

            return node;
        }

        private Node moveRedRight(Node node) {
            // Assuming that node is red and both node.right and node.right.left are black,
            // make node.right or one of its children red
            flipColors(node);

            if (node.left != null && isRed(node.left.left)) {
                node = rotateRight(node);
                flipColors(node);
            }

            return node;
        }

        private Node balance(Node node) {
            if (node == null) {
                return null;
            }

            if (isRed(node.right)) {
                node = rotateLeft(node);
            }

            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }

            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);

            return node;
        }

        public int[] keys() {
            return keys(min(), max());
        }

        public int[] keys(int low, int high) {
            if (low == EMPTY_KEY)  {
                throw new IllegalArgumentException("Invalid key on the first argument");
            }
            if (high == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key on the second argument");
            }

            Queue<Integer> queue = new Queue<>();
            keys(root, queue, low, high);

            int[] keys = new int[queue.size()];
            for (int i = 0; i < keys.length; i++) {
                keys[i] = queue.dequeue();
            }

            return keys;
        }

        private void keys(Node node, Queue<Integer> queue, int low, int high) {
            if (node == null) {
                return;
            }

            int compareLow;

            if (low < node.key) {
                compareLow = -1;
            } else if (low > node.key) {
                compareLow = 1;
            } else {
                compareLow = 0;
            }

            int compareHigh;

            if (high < node.key) {
                compareHigh = -1;
            } else if (high > node.key) {
                compareHigh = 1;
            } else {
                compareHigh = 0;
            }

            if (compareLow < 0) {
                keys(node.left, queue, low, high);
            }

            if (compareLow <= 0 && compareHigh >= 0) {
                queue.enqueue(node.key);
            }

            if (compareHigh > 0) {
                keys(node.right, queue, low, high);
            }
        }

        public int size(int low, int high) {
            if (low == EMPTY_KEY)  {
                throw new IllegalArgumentException("Invalid key on the first argument");
            }
            if (high == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key on the second argument");
            }

            if (low > high) {
                return 0;
            }

            if (contains(high)) {
                return rank(high) - rank(low) + 1;
            } else {
                return rank(high) - rank(low);
            }
        }

    }

    private class SETdouble {
        private static final boolean RED = true;
        private static final boolean BLACK = false;

        public final static double EMPTY_KEY = Double.MIN_VALUE;

        private class Node {
            double key;
            Node left, right;

            boolean color;
            int size;

            Node(double key, int size, boolean color) {
                this.key = key;

                this.size = size;
                this.color = color;
            }
        }

        private Node root;

        public int size() {
            return size(root);
        }

        private int size(Node node) {
            if (node == null) {
                return 0;
            }

            return node.size;
        }

        public boolean isEmpty() {
            return size(root) == 0;
        }

        private boolean isRed(Node node) {
            if (node == null) {
                return false;
            }

            return node.color == RED;
        }

        private Node rotateLeft(Node node) {
            if (node == null || node.right == null) {
                return node;
            }

            Node newRoot = node.right;

            node.right = newRoot.left;
            newRoot.left = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private Node rotateRight(Node node) {
            if (node == null || node.left == null) {
                return node;
            }

            Node newRoot = node.left;

            node.left = newRoot.right;
            newRoot.right = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private void flipColors(Node node) {
            if (node == null || node.left == null || node.right == null) {
                return;
            }

            // The root must have opposite color of its two children
            if ((isRed(node) && !isRed(node.left) && !isRed(node.right))
                    || (!isRed(node) && isRed(node.left) && isRed(node.right))) {
                node.color = !node.color;
                node.left.color = !node.left.color;
                node.right.color = !node.right.color;
            }
        }

        public void add(double key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            root = add(root, key);
            root.color = BLACK;
        }

        private Node add(Node node, double key) {
            if (node == null) {
                return new Node(key, 1, RED);
            }

            if (key < node.key) {
                node.left = add(node.left, key);
            } else if (key > node.key) {
                node.right = add(node.right, key);
            } else {
                node.key = key;
            }

            if (isRed(node.right) && !isRed(node.left)) {
                node = rotateLeft(node);
            }
            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }
            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        public boolean contains(double key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            Node currentNode = root;
            while (currentNode != null) {
                if (key < currentNode.key) {
                    currentNode = currentNode.left;
                } else if (key > currentNode.key) {
                    currentNode = currentNode.right;
                } else {
                    return true;
                }
            }

            return false;
        }

        public double min() {
            if (root == null) {
                throw new NoSuchElementException("Empty set");
            }

            return min(root).key;
        }

        private Node min(Node node) {
            if (node.left == null) {
                return node;
            }

            return min(node.left);
        }

        public double max() {
            if (root == null) {
                throw new NoSuchElementException("Empty set");
            }

            return max(root).key;
        }

        private Node max(Node node) {
            if (node.right == null) {
                return node;
            }

            return max(node.right);
        }

        // Returns the highest key in the set smaller than or equal to key.
        public double floor(double key) {
            Node node = floor(root, key);
            if (node == null) {
                return EMPTY_KEY;
            }
            return node.key;
        }

        private Node floor(Node node, double key) {
            if (node == null) {
                return null;
            }

            if (key == node.key) {
                return node;
            } else if (key < node.key) {
                return floor(node.left, key);
            } else {
                Node rightNode = floor(node.right, key);
                if (rightNode != null) {
                    return rightNode;
                } else {
                    return node;
                }
            }
        }

        // Returns the smallest key in the set greater than or equal to key.
        public double ceiling(double key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return EMPTY_KEY;
            }
            return node.key;
        }

        private Node ceiling(Node node, double key) {
            if (node == null) {
                return null;
            }

            if (key == node.key) {
                return node;
            } else if (key > node.key) {
                return ceiling(node.right, key);
            } else {
                Node leftNode = ceiling(node.left, key);
                if (leftNode != null) {
                    return leftNode;
                } else {
                    return node;
                }
            }
        }

        public double select(int index) {
            if (index >= size()) {
                throw new IllegalArgumentException("Index is higher than set size");
            }

            return select(root, index).key;
        }

        private Node select(Node node, int index) {
            int leftSubtreeSize = size(node.left);

            if (leftSubtreeSize == index) {
                return node;
            } else if (leftSubtreeSize > index) {
                return select(node.left, index);
            } else {
                return select(node.right, index - leftSubtreeSize - 1);
            }
        }

        public int rank(double key) {
            return rank(root, key);
        }

        private int rank(Node node, double key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            if (key < node.key) {
                return rank(node.left, key);
            } else if (key > node.key) {
                return size(node.left) + 1 + rank(node.right, key);
            } else {
                return size(node.left);
            }
        }

        public void deleteMin() {
            if (isEmpty()) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = deleteMin(root);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node deleteMin(Node node) {
            if (node.left == null) {
                return null;
            }

            if (!isRed(node.left) && !isRed(node.left.left)) {
                node = moveRedLeft(node);
            }

            node.left = deleteMin(node.left);
            return balance(node);
        }

        public void deleteMax() {
            if (isEmpty()) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = deleteMax(root);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node deleteMax(Node node) {
            if (isRed(node.left)) {
                node = rotateRight(node);
            }

            if (node.right == null) {
                return null;
            }

            if (!isRed(node.right) && !isRed(node.right.left)) {
                node = moveRedRight(node);
            }

            node.right = deleteMax(node.right);
            return balance(node);
        }

        public void delete(double key) {
            if (key == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key");
            }

            if (isEmpty() || !contains(key)) {
                return;
            }

            if (!isRed(root.left) && !isRed(root.right)) {
                root.color = RED;
            }

            root = delete(root, key);

            if (!isEmpty()) {
                root.color = BLACK;
            }
        }

        private Node delete(Node node, double key) {
            if (node == null) {
                return null;
            }

            if (key < node.key) {
                if (!isRed(node.left) && node.left != null && !isRed(node.left.left)) {
                    node = moveRedLeft(node);
                }

                node.left = delete(node.left, key);
            } else {
                if (isRed(node.left)) {
                    node = rotateRight(node);
                }

                if (key == node.key && node.right == null) {
                    return null;
                }

                if (!isRed(node.right) && node.right != null && !isRed(node.right.left)) {
                    node = moveRedRight(node);
                }

                if (key == node.key) {
                    Node aux = min(node.right);
                    node.key = aux.key;
                    node.right = deleteMin(node.right);
                } else {
                    node.right = delete(node.right, key);
                }
            }

            return balance(node);
        }

        private Node moveRedLeft(Node node) {
            // Assuming that node is red and both node.left and node.left.left are black,
            // make node.left or one of its children red
            flipColors(node);

            if (node.right != null && isRed(node.right.left)) {
                node.right = rotateRight(node.right);
                node = rotateLeft(node);
                flipColors(node);
            }

            return node;
        }

        private Node moveRedRight(Node node) {
            // Assuming that node is red and both node.right and node.right.left are black,
            // make node.right or one of its children red
            flipColors(node);

            if (node.left != null && isRed(node.left.left)) {
                node = rotateRight(node);
                flipColors(node);
            }

            return node;
        }

        private Node balance(Node node) {
            if (node == null) {
                return null;
            }

            if (isRed(node.right)) {
                node = rotateLeft(node);
            }

            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }

            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);

            return node;
        }

        public double[] keys() {
            return keys(min(), max());
        }

        public double[] keys(double low, double high) {
            if (low == EMPTY_KEY)  {
                throw new IllegalArgumentException("Invalid key on the first argument");
            }
            if (high == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key on the second argument");
            }

            Queue<Double> queue = new Queue<>();
            keys(root, queue, low, high);

            double[] keys = new double[queue.size()];
            for (int i = 0; i < keys.length; i++) {
                keys[i] = queue.dequeue();
            }
            return keys;
        }

        private void keys(Node node, Queue<Double> queue, double low, double high) {
            if (node == null) {
                return;
            }

            int compareLow;

            if (low < node.key) {
                compareLow = -1;
            } else if (low > node.key) {
                compareLow = 1;
            } else {
                compareLow = 0;
            }

            int compareHigh;

            if (high < node.key) {
                compareHigh = -1;
            } else if (high > node.key) {
                compareHigh = 1;
            } else {
                compareHigh = 0;
            }

            if (compareLow < 0) {
                keys(node.left, queue, low, high);
            }

            if (compareLow <= 0 && compareHigh >= 0) {
                queue.enqueue(node.key);
            }

            if (compareHigh > 0) {
                keys(node.right, queue, low, high);
            }
        }

        public int size(double low, double high) {
            if (low == EMPTY_KEY)  {
                throw new IllegalArgumentException("Invalid key on the first argument");
            }
            if (high == EMPTY_KEY) {
                throw new IllegalArgumentException("Invalid key on the second argument");
            }

            if (low > high) {
                return 0;
            }

            if (contains(high)) {
                return rank(high) - rank(low) + 1;
            } else {
                return rank(high) - rank(low);
            }
        }
    }

    public static void main(String[] args) {
        Exercise7 exercise7 = new Exercise7();
        exercise7.testSETint();
        exercise7.testSETdouble();
    }

    private void testSETint() {
        StdOut.println("SETint test");

        SETint setInt = new SETint();

        setInt.add(5);
        setInt.add(1);
        setInt.add(9);
        setInt.add(2);
        setInt.add(0);
        setInt.add(99);
        setInt.add(-1);
        setInt.add(-2);
        setInt.add(3);
        setInt.add(-5);

        StdOut.println("\nKeys() test");

        for (Integer key : setInt.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nExpected: -5 -2 -1 0 1 2 3 5 9 99");

        // Test min()
        StdOut.println("\nMin key: " + setInt.min() + " Expected: -5");

        // Test max()
        StdOut.println("Max key: " + setInt.max() + " Expected: 99");

        // Test floor()
        StdOut.println("Floor of 5: " + setInt.floor(5) + " Expected: 5");
        StdOut.println("Floor of 15: " + setInt.floor(15) + " Expected: 9");

        // Test ceiling()
        StdOut.println("Ceiling of 5: " + setInt.ceiling(5) + " Expected: 5");
        StdOut.println("Ceiling of 15: " + setInt.ceiling(15) + " Expected: 99");

        // Test select()
        StdOut.println("Select key of rank 4: " + setInt.select(4) + " Expected: 1");

        // Test rank()
        StdOut.println("Rank of key 9: " + setInt.rank(9) + " Expected: 8");
        StdOut.println("Rank of key 10: " + setInt.rank(10) + " Expected: 9");

        // Test delete()
        StdOut.println("\nDelete key 2");
        setInt.delete(2);

        for (Integer key : setInt.keys()) {
            StdOut.print(key + " ");
        }

        // Test deleteMin()
        StdOut.println("\n\nDelete min (key -5)");
        setInt.deleteMin();

        for (Integer key : setInt.keys()) {
            StdOut.print(key + " ");
        }

        // Test deleteMax()
        StdOut.println("\n\nDelete max (key 99)");
        setInt.deleteMax();

        for (Integer key : setInt.keys()) {
            StdOut.print(key + " ");
        }

        // Test keys() with range
        StdOut.println("\n\nKeys in range [2, 10]");
        for (Integer key : setInt.keys(2, 10)) {
            StdOut.print(key + " ");
        }

        StdOut.println("\n\nKeys in range [-4, -1]");
        for (Integer key : setInt.keys(-4, -1)) {
            StdOut.print(key + " ");
        }

        // Delete all
        StdOut.println("\n\nDelete all");
        while (setInt.size() > 0) {
            for (Integer key : setInt.keys()) {
                StdOut.print(key + " ");
            }
            // setInt.delete(setInt.select(0));
            setInt.delete(setInt.select(setInt.size() - 1));
            StdOut.println();
        }
    }

    private void testSETdouble() {
        StdOut.println("\nSETdouble test");

        SETdouble setDouble = new SETdouble();

        setDouble.add(5.0);
        setDouble.add(1.0);
        setDouble.add(9.5);
        setDouble.add(2.1);
        setDouble.add(0);
        setDouble.add(99.999);
        setDouble.add(-1.05);
        setDouble.add(-2.20);
        setDouble.add(3.0);
        setDouble.add(-5.9);

        StdOut.println("\nKeys() test");

        for (Double key : setDouble.keys()) {
            StdOut.print(key + " ");
        }
        StdOut.println("\nExpected: -5.9 -2.2 -1.05 0.0 1.0 2.1 3.0 5.0 9.5 99.999");

        // Test min()
        StdOut.println("\nMin key: " + setDouble.min() + " Expected: -5.9");

        // Test max()
        StdOut.println("Max key: " + setDouble.max() + " Expected: 99.999");

        // Test floor()
        StdOut.println("Floor of 5.0: " + setDouble.floor(5.0) + " Expected: 5.0");
        StdOut.println("Floor of 15.0: " + setDouble.floor(15.0) + " Expected: 9.5");

        // Test ceiling()
        StdOut.println("Ceiling of 5.0: " + setDouble.ceiling(5.0) + " Expected: 5.0");
        StdOut.println("Ceiling of 15.0: " + setDouble.ceiling(15.0) + " Expected: 99.999");

        // Test select()
        StdOut.println("Select key of rank 4: " + setDouble.select(4) + " Expected: 1.0");

        // Test rank()
        StdOut.println("Rank of key 9: " + setDouble.rank(9.5) + " Expected: 8");
        StdOut.println("Rank of key 10: " + setDouble.rank(10.0) + " Expected: 9");

        // Test delete()
        StdOut.println("\nDelete key 2.1");
        setDouble.delete(2.1);

        for (Double key : setDouble.keys()) {
            StdOut.print(key + " ");
        }

        // Test deleteMin()
        StdOut.println("\n\nDelete min (key -5.9)");
        setDouble.deleteMin();

        for (Double key : setDouble.keys()) {
            StdOut.print(key + " ");
        }

        // Test deleteMax()
        StdOut.println("\n\nDelete max (key 99.999)");
        setDouble.deleteMax();

        for (Double key : setDouble.keys()) {
            StdOut.print(key + " ");
        }

        // Test keys() with range
        StdOut.println("\n\nKeys in range [2.0, 10.0]");
        for (Double key : setDouble.keys(2.0, 10.0)) {
            StdOut.print(key + " ");
        }

        StdOut.println("\n\nKeys in range [-4.0, -1.0]");
        for (Double key : setDouble.keys(-4.0, -1.0)) {
            StdOut.print(key + " ");
        }

        // Delete all
        StdOut.println("\n\nDelete all");
        while (setDouble.size() > 0) {
            for (Double key : setDouble.keys()) {
                StdOut.print(key + " ");
            }
            // setDouble.delete(setDouble.select(0));
            setDouble.delete(setDouble.select(setDouble.size() - 1));
            StdOut.println();
        }
    }
}


Question: Modify LinearProbingHashST to keep duplicate keys in the table. Return any value associated with the given key for get(), and remove all items in the table that have keys equal to the given key for delete().

Answer:
package chapter3.section5;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;

import java.util.Arrays;

/**
 * Created by Rene Argento on 05/08/17.
 */
public class Exercise8 {

    @SuppressWarnings("unchecked")
    private class LinearProbingHashTableDuplicateKeys<Key, Value> {

        private int keysSize;
        private int size;

        private Key[] keys;
        private Value[] values;

        //The largest prime <= 2^i for i = 1 to 31
        //Used to distribute keys uniformly in the hash table after resizes
        //PRIMES[n] = 2^k - Ak where k is the power of 2 and Ak is the value to subtract to reach the previous prime number
        private final int[] PRIMES = {
                1, 1, 3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381,
                32749, 65521, 131071, 262139, 524287, 1048573, 2097143, 4194301,
                8388593, 16777213, 33554393, 67108859, 134217689, 268435399,
                536870909, 1073741789, 2147483647
        };

        //The lg of the hash table size
        //Used in combination with PRIMES[] to distribute keys uniformly in the hash function after resizes
        private int lgM;

        LinearProbingHashTableDuplicateKeys(int size) {
            this.size = size;
            keys = (Key[]) new Object[size];
            values = (Value[]) new Object[size];

            lgM = (int) (Math.log(size) / Math.log(2));
        }

        public int size() {
            return keysSize;
        }

        public boolean isEmpty() {
            return keysSize == 0;
        }

        private int hash(Key key) {
            int hash = key.hashCode() & 0x7fffffff;

            if (lgM < 26) {
                hash = hash % PRIMES[lgM + 5];
            }

            return hash % size;
        }

        private double getLoadFactor() {
            return keysSize / (double) size;
        }

        private void resize(int newSize) {
            LinearProbingHashTableDuplicateKeys<Key, Value> tempHashTable = new LinearProbingHashTableDuplicateKeys<>(newSize);

            for (int i = 0; i < size; i++) {
                if (keys[i] != null) {
                    tempHashTable.put(keys[i], values[i]);
                }
            }

            keys = tempHashTable.keys;
            values = tempHashTable.values;
            size = tempHashTable.size;
        }

        public boolean contains(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to contains() cannot be null");
            }

            return get(key) != null;
        }

        public Value get(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to get() cannot be null");
            }

            for (int tableIndex = hash(key); keys[tableIndex] != null; tableIndex = (tableIndex + 1) % size) {
                if (keys[tableIndex].equals(key)) {
                    return values[tableIndex];
                }
            }

            return null;
        }

        public void put(Key key, Value value) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (value == null) {
                delete(key);
                return;
            }

            if (keysSize >= size / (double) 2) {
                resize(size * 2);
                lgM++;
            }

            int tableIndex = hash(key);
            while (keys[tableIndex] != null) {
                tableIndex = (tableIndex + 1) % size;
            }

            keys[tableIndex] = key;
            values[tableIndex] = value;

            keysSize++;
        }

        public void delete(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to delete() cannot be null");
            }

            if (!contains(key)) {
                return;
            }

            int tableIndex = hash(key);
            while (!keys[tableIndex].equals(key)) {
                tableIndex = (tableIndex + 1) % size;
            }

            keys[tableIndex] = null;
            values[tableIndex] = null;
            keysSize--;

            tableIndex = (tableIndex + 1) % size;

            while (keys[tableIndex] != null) {
                Key keyToRedo = keys[tableIndex];
                Value valueToRedo = values[tableIndex];

                keys[tableIndex] = null;
                values[tableIndex] = null;
                keysSize--;

                if (!keyToRedo.equals(key)) {
                    put(keyToRedo, valueToRedo);
                }

                tableIndex = (tableIndex + 1) % size;
            }

            if (keysSize > 1 && keysSize <= size / (double) 8) {
                resize(size / 2);
                lgM--;
            }
        }

        public Iterable<Key> keys() {
            Queue<Key> keySet = new Queue<>();

            for (Object key : keys) {
                if (key != null) {
                    keySet.enqueue((Key) key);
                }
            }

            if (!keySet.isEmpty() && keySet.peek() instanceof Comparable) {
                Key[] keysToBeSorted = (Key[]) new Comparable[keySet.size()];
                for (int i = 0; i < keysToBeSorted.length; i++) {
                    keysToBeSorted[i] = keySet.dequeue();
                }

                Arrays.sort(keysToBeSorted);

                for (Key key : keysToBeSorted) {
                    keySet.enqueue(key);
                }
            }
            return keySet;
        }
    }

    public static void main(String[] args) {
        Exercise8 exercise8 = new Exercise8();
        LinearProbingHashTableDuplicateKeys<Integer, Integer> linearProbingHashTableDuplicateKeys =
                exercise8.new LinearProbingHashTableDuplicateKeys<>(10);

        // Test put()
        linearProbingHashTableDuplicateKeys.put(0, 0);
        linearProbingHashTableDuplicateKeys.put(0, 1);
        linearProbingHashTableDuplicateKeys.put(0, 2);
        linearProbingHashTableDuplicateKeys.put(0, 3);

        linearProbingHashTableDuplicateKeys.put(5, 7);
        linearProbingHashTableDuplicateKeys.put(5, 8);

        linearProbingHashTableDuplicateKeys.put(8, 9);
        linearProbingHashTableDuplicateKeys.put(8, 10);

        linearProbingHashTableDuplicateKeys.put(20, 11);
        linearProbingHashTableDuplicateKeys.put(20, 12);
        linearProbingHashTableDuplicateKeys.put(20, 13);
        linearProbingHashTableDuplicateKeys.put(20, 14);

        // Test resize()
        linearProbingHashTableDuplicateKeys.put(21, 15);
        linearProbingHashTableDuplicateKeys.put(22, 16);
        linearProbingHashTableDuplicateKeys.put(23, 17);
        linearProbingHashTableDuplicateKeys.put(24, 18);

        for (Integer key : linearProbingHashTableDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + linearProbingHashTableDuplicateKeys.get(key));
        }

        StdOut.println("\nExpected:");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 5: 7");
        StdOut.println("Key 5: 7");
        StdOut.println("Key 8: 9");
        StdOut.println("Key 8: 9");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 21: 15");
        StdOut.println("Key 22: 16");
        StdOut.println("Key 23: 17");
        StdOut.println("Key 24: 18");

        // Test size()
        StdOut.println("Keys size: " + linearProbingHashTableDuplicateKeys.size() + " Expected: 16");

        // Test contains()
        StdOut.println("\nContains 8: " + linearProbingHashTableDuplicateKeys.contains(8) + " Expected: true");
        StdOut.println("Contains 9: " + linearProbingHashTableDuplicateKeys.contains(9) + " Expected: false");

        // Test delete
        StdOut.println("\nDelete key 20");
        linearProbingHashTableDuplicateKeys.delete(20);
        StdOut.println("Keys size: " + linearProbingHashTableDuplicateKeys.size() + " Expected: 12");

        StdOut.println("\nDelete key 5");
        linearProbingHashTableDuplicateKeys.delete(5);
        StdOut.println("Keys size: " + linearProbingHashTableDuplicateKeys.size() + " Expected: 10");
    }
}


Question: Modify BST to keep duplicate keys in the tree. Return any value associated with the given key for get(), and remove all nodes in the tree that have keys equal to the given key for delete().

Answer:
package chapter3.section5;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 05/08/17.
 */
public class Exercise9 {

    /**
     * This tree has the following property:
     *
     * Let x be a node in the binary search tree.
     * If y is a node in the left subtree of x, then y:key <= x:key.
     * If y is a node in the right subtree of x, then y:key > x:key.
     */
    private class BinarySearchTreeDuplicateKeys<Key extends Comparable<Key>, Value> {

        private class Node {
            private Key key;
            private Value value;

            private Node left;
            private Node right;

            private int size; //# of nodes in subtree rooted here

            public Node(Key key, Value value, int size) {
                this.key = key;
                this.value = value;
                this.size = size;
            }
        }

        private Node root;

        public int size() {
            return size(root);
        }

        private int size(Node node) {
            if (node == null) {
                return 0;
            }

            return node.size;
        }

        public boolean isEmpty() {
            return size(root) == 0;
        }

        public Value get(Key key) {
            if (key == null) {
                return null;
            }

            return get(root, key);
        }

        private Value get(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return get(node.left, key);
            } else if (compare > 0) {
                return get(node.right, key);
            } else {
                return node.value;
            }
        }

        public boolean contains(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to contains() cannot be null");
            }
            return get(key) != null;
        }

        public void put(Key key, Value value) {
            if (key == null) {
                return;
            }

            if (value == null) {
                delete(key);
                return;
            }

            root = put(root, key, value);
        }

        private Node put(Node node, Key key, Value value) {
            if (node == null) {
                return new Node(key, value, 1);
            }

            int compare = key.compareTo(node.key);

            // If it is a duplicate key, put it on the left subtree
            if (compare <= 0) {
                node.left = put(node.left, key, value);
            } else if (compare > 0) {
                node.right = put(node.right, key, value);
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        public Key min() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return min(root).key;
        }

        private Node min(Node node) {
            if (node.left == null) {
                return node;
            }

            return min(node.left);
        }

        public Key max() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return max(root).key;
        }

        private Node max(Node node) {
            if (node.right == null) {
                return node;
            }

            return max(node.right);
        }

        // Returns the highest key in the symbol table smaller than or equal to key.
        public Key floor(Key key) {
            Node node = floor(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node floor(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare < 0) {
                return floor(node.left, key);
            } else {
                Node rightNode = floor(node.right, key);
                if (rightNode != null) {
                    return rightNode;
                } else {
                    return node;
                }
            }
        }

        // Returns the smallest key in the symbol table greater than or equal to key.
        public Key ceiling(Key key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node ceiling(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare > 0) {
                return ceiling(node.right, key);
            } else {
                Node leftNode = ceiling(node.left, key);
                if (leftNode != null) {
                    return leftNode;
                } else {
                    return node;
                }
            }
        }

        public Key select(int index) {
            if (index >= size()) {
                throw new IllegalArgumentException("Index is higher than tree size");
            }

            return select(root, index).key;
        }

        private Node select(Node node, int index) {
            int leftSubtreeSize = size(node.left);

            if (leftSubtreeSize == index) {
                return node;
            } else if (leftSubtreeSize > index) {
                return select(node.left, index);
            } else {
                return select(node.right, index - leftSubtreeSize - 1);
            }
        }

        public int rankFirst(Key key) {
            return rankFirst(root, key);
        }

        private int rankFirst(Node node, Key key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return rankFirst(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankFirst(node.right, key);
            } else {
                boolean hasDuplicateOnLeftSubtree = false;

                if (node.left != null && max(node.left).key.compareTo(key) == 0) {
                    hasDuplicateOnLeftSubtree = true;
                }

                if (hasDuplicateOnLeftSubtree) {
                    return rankFirst(node.left, key);
                } else {
                    return size(node.left);
                }
            }
        }

        public int rankLast(Key key) {
            return rankLast(root, key);
        }

        private int rankLast(Node node, Key key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return rankLast(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankLast(node.right, key);
            } else {
                boolean hasDuplicateOnRightSubtree = false;

                if (node.right != null && min(node.right).key.compareTo(key) == 0) {
                    hasDuplicateOnRightSubtree = true;
                }

                if (hasDuplicateOnRightSubtree) {
                    return size(node.left) + 1 + rankLast(node.right, key);
                } else {
                    return size(node.left);
                }
            }
        }

        // In the case of duplicates, return the rank of the rightmost key
        public int rank(Key key) {
            return rankLast(key);
        }

        // O(n lg n) since we are removing all duplicate min keys
        public void deleteMin() {
            if (root == null) {
                return;
            }

            Key minKey = min();

            while (contains(minKey)) {
                root = deleteMin(root);
            }
        }

        private Node deleteMin(Node node) {
            if (node.left == null) {
                return node.right;
            }

            node.left = deleteMin(node.left);
            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        // O(n lg n) since we are removing all duplicate max keys
        public void deleteMax() {
            if (root == null) {
                return;
            }

            Key maxKey = max();

            while (contains(maxKey)) {
                root = deleteMax(root);
            }
        }

        private Node deleteMax(Node node) {
            if (node.right == null) {
                return node.left;
            }

            node.right = deleteMax(node.right);
            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        // O(n lg n) since we are removing all duplicate keys
        public void delete(Key key) {
            if (isEmpty()) {
                return;
            }

            while (contains(key)) {
                root = delete(root, key);
            }
        }

        private Node delete(Node node, Key key) {
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                node.left = delete(node.left, key);
            } else if (compare > 0) {
                node.right = delete(node.right, key);
            } else {
                if (node.left == null) {
                    return node.right;
                } else if (node.right == null) {
                    return node.left;
                } else {
                    Node aux = node;
                    node = min(aux.right);
                    node.right = deleteMin(aux.right);
                    node.left = aux.left;
                }
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        public Iterable<Key> keys() {
            return keys(min(), max());
        }

        public Iterable<Key> keys(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to keys() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to keys() cannot be null");
            }

            Queue<Key> queue = new Queue<>();
            keys(root, queue, low, high);
            return queue;
        }

        private void keys(Node node, Queue<Key> queue, Key low, Key high) {
            if (node == null) {
                return;
            }

            int compareLow = low.compareTo(node.key);
            int compareHigh = high.compareTo(node.key);

            if (compareLow <= 0) {
                keys(node.left, queue, low, high);
            }

            if (compareLow <= 0 && compareHigh >= 0) {
                queue.enqueue(node.key);
            }

            if (compareHigh >= 0) {
                keys(node.right, queue, low, high);
            }
        }

        public int size(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to size() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to size() cannot be null");
            }

            if (low.compareTo(high) > 0) {
                return 0;
            }

            if (contains(high)) {
                return rankLast(high) - rankFirst(low) + 1;
            } else {
                return rankLast(high) - rankFirst(low);
            }
        }
    }

    public static void main(String[] args) {
        Exercise9 exercise9 = new Exercise9();
        BinarySearchTreeDuplicateKeys<Integer, Integer> binarySearchTreeDuplicateKeys =
                exercise9.new BinarySearchTreeDuplicateKeys<>();

        // Test put()
        binarySearchTreeDuplicateKeys.put(0, 0);
        binarySearchTreeDuplicateKeys.put(0, 1);
        binarySearchTreeDuplicateKeys.put(0, 2);
        binarySearchTreeDuplicateKeys.put(0, 3);

        binarySearchTreeDuplicateKeys.put(5, 7);
        binarySearchTreeDuplicateKeys.put(5, 8);

        binarySearchTreeDuplicateKeys.put(8, 9);
        binarySearchTreeDuplicateKeys.put(8, 10);

        binarySearchTreeDuplicateKeys.put(20, 11);
        binarySearchTreeDuplicateKeys.put(20, 12);
        binarySearchTreeDuplicateKeys.put(20, 13);
        binarySearchTreeDuplicateKeys.put(20, 14);

        binarySearchTreeDuplicateKeys.put(21, 15);
        binarySearchTreeDuplicateKeys.put(22, 16);
        binarySearchTreeDuplicateKeys.put(23, 17);
        binarySearchTreeDuplicateKeys.put(24, 18);

        StdOut.println("Keys() test");
        for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }
        StdOut.println("\nExpected:");
        // When there are duplicate keys the expected value is the value of the first inserted key
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 0: 0");
        StdOut.println("Key 5: 7");
        StdOut.println("Key 5: 7");
        StdOut.println("Key 8: 9");
        StdOut.println("Key 8: 9");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 20: 11");
        StdOut.println("Key 21: 15");
        StdOut.println("Key 22: 16");
        StdOut.println("Key 23: 17");
        StdOut.println("Key 24: 18");

        // Test size()
        StdOut.println("Keys size: " + binarySearchTreeDuplicateKeys.size() + " Expected: 16");

        // Test size() with range
        StdOut.println("Keys size [0, 20]: " + binarySearchTreeDuplicateKeys.size(0, 20) + " Expected: 12");

        // Test contains()
        StdOut.println("\nContains 8: " + binarySearchTreeDuplicateKeys.contains(8) + " Expected: true");
        StdOut.println("Contains 9: " + binarySearchTreeDuplicateKeys.contains(9) + " Expected: false");

        // Test min()
        StdOut.println("\nMin key: " + binarySearchTreeDuplicateKeys.min() + " Expected: 0");

        // Test max()
        StdOut.println("Max key: " + binarySearchTreeDuplicateKeys.max() + " Expected: 24");

        // Test floor()
        StdOut.println("Floor of 5: " + binarySearchTreeDuplicateKeys.floor(5) + " Expected: 5");
        StdOut.println("Floor of 15: " + binarySearchTreeDuplicateKeys.floor(15) + " Expected: 8");

        // Test ceiling()
        StdOut.println("Ceiling of 5: " + binarySearchTreeDuplicateKeys.ceiling(5) + " Expected: 5");
        StdOut.println("Ceiling of 15: " + binarySearchTreeDuplicateKeys.ceiling(15) + " Expected: 20");

        // Test select()
        StdOut.println("Select key of rank 3: " + binarySearchTreeDuplicateKeys.select(3) + " Expected: 0");
        StdOut.println("Select key of rank 4: " + binarySearchTreeDuplicateKeys.select(4) + " Expected: 5");

        // Test rank()
        // Note that the expected rank of key 8 is 7 and not 6, because we are assuming that rank returns the index
        // of the rightmost key when there are duplicates
        StdOut.println("Rank of key 8: " + binarySearchTreeDuplicateKeys.rank(8) + " Expected: 7");
        StdOut.println("Rank of key 9: " + binarySearchTreeDuplicateKeys.rank(9) + " Expected: 8");

        // Test delete()
        StdOut.println("\nDelete key 20");
        binarySearchTreeDuplicateKeys.delete(20);

        for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + binarySearchTreeDuplicateKeys.size() + " Expected: 12");

        StdOut.println("\nDelete key 5");
        binarySearchTreeDuplicateKeys.delete(5);

        for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + binarySearchTreeDuplicateKeys.size() + " Expected: 10");

        // Test deleteMin()
        StdOut.println("\nDelete min (key 0)");
        binarySearchTreeDuplicateKeys.deleteMin();

        for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }

        // Test deleteMax()
        StdOut.println("\nDelete max (key 24)");
        binarySearchTreeDuplicateKeys.deleteMax();

        for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }

        // Test keys() with range
        StdOut.println("\nKeys in range [2, 10]");
        for (Integer key : binarySearchTreeDuplicateKeys.keys(2, 10)) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }

        StdOut.println("\nKeys in range [20, 22]");
        for (Integer key : binarySearchTreeDuplicateKeys.keys(20, 22)) {
            StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
        }

        // Delete all
        StdOut.println("\nDelete all");
        while (binarySearchTreeDuplicateKeys.size() > 0) {
            for (Integer key : binarySearchTreeDuplicateKeys.keys()) {
                StdOut.println("Key " + key + ": " + binarySearchTreeDuplicateKeys.get(key));
            }
            // binarySearchTreeDuplicateKeys.delete(binarySearchTreeDuplicateKeys.select(0));
            binarySearchTreeDuplicateKeys.delete(binarySearchTreeDuplicateKeys.select(binarySearchTreeDuplicateKeys.size() - 1));
            StdOut.println();
        }
    }
}


Question: Modify RedBlackBST to keep duplicate keys in the tree. Return any value associated with the given key for get(), and remove all nodes in the tree that have keys equal to the given key for delete().

Answer:
package chapter3.section5;

import edu.princeton.cs.algs4.Queue;
import edu.princeton.cs.algs4.StdOut;

import java.util.NoSuchElementException;

/**
 * Created by Rene Argento on 05/08/17.
 */
public class Exercise10 {

    /**
     * This tree has the following property:
     *
     * Let x be a node in the red-black binary search tree.
     * If y is a node in the left subtree of x, then y:key <= x:key.
     * If y is a node in the right subtree of x, then y:key >= x:key.
     */
    private class RedBlackBSTDuplicateKeys<Key extends Comparable<Key>, Value> {

        private static final boolean RED = true;
        private static final boolean BLACK = false;

        private class Node {
            Key key;
            Value value;
            Node left, right;

            boolean color;
            int size;

            Node(Key key, Value value, int size, boolean color) {
                this.key = key;
                this.value = value;

                this.size = size;
                this.color = color;
            }
        }

        private Node root;

        public int size() {
            return size(root);
        }

        private int size(Node node) {
            if (node == null) {
                return 0;
            }

            return node.size;
        }

        public boolean isEmpty() {
            return size(root) == 0;
        }

        private boolean isRed(Node node) {
            if (node == null) {
                return false;
            }

            return node.color == RED;
        }

        private Node rotateLeft(Node node) {
            if (node == null || node.right == null) {
                return node;
            }

            Node newRoot = node.right;

            node.right = newRoot.left;
            newRoot.left = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private Node rotateRight(Node node) {
            if (node == null || node.left == null) {
                return node;
            }

            Node newRoot = node.left;

            node.left = newRoot.right;
            newRoot.right = node;

            newRoot.color = node.color;
            node.color = RED;

            newRoot.size = node.size;
            node.size = size(node.left) + 1 + size(node.right);

            return newRoot;
        }

        private void flipColors(Node node) {
            if (node == null || node.left == null || node.right == null) {
                return;
            }

            // The root must have opposite color of its two children
            if ((isRed(node) && !isRed(node.left) && !isRed(node.right))
                    || (!isRed(node) && isRed(node.left) && isRed(node.right))) {
                node.color = !node.color;
                node.left.color = !node.left.color;
                node.right.color = !node.right.color;
            }
        }

        public void put(Key key, Value value) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (value == null) {
                delete(key);
                return;
            }

            root = put(root, key, value);
            root.color = BLACK;
        }

        private Node put(Node node, Key key, Value value) {
            if (node == null) {
                return new Node(key, value, 1, RED);
            }

            int compare = key.compareTo(node.key);

            // If it is a duplicate key, put it on the left subtree
            // It is important to notice that since we have rotations,
            // the duplicate keys may be on the left or right subtrees later
            if (compare <= 0) {
                node.left = put(node.left, key, value);
            } else if (compare > 0) {
                node.right = put(node.right, key, value);
            }

            if (isRed(node.right) && !isRed(node.left)) {
                node = rotateLeft(node);
            }
            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }
            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);
            return node;
        }

        public Value get(Key key) {
            if (key == null) {
                return null;
            }

            return get(root, key);
        }

        private Value get(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return get(node.left, key);
            } else if (compare > 0) {
                return get(node.right, key);
            } else {
                return node.value;
            }
        }

        public boolean contains(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Argument to contains() cannot be null");
            }
            return get(key) != null;
        }

        public Key min() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return min(root).key;
        }

        private Node min(Node node) {
            if (node.left == null) {
                return node;
            }

            return min(node.left);
        }

        public Key max() {
            if (root == null) {
                throw new NoSuchElementException("Empty binary search tree");
            }

            return max(root).key;
        }

        private Node max(Node node) {
            if (node.right == null) {
                return node;
            }

            return max(node.right);
        }

        // Returns the highest key in the symbol table smaller than or equal to key.
        public Key floor(Key key) {
            Node node = floor(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node floor(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare < 0) {
                return floor(node.left, key);
            } else {
                Node rightNode = floor(node.right, key);
                if (rightNode != null) {
                    return rightNode;
                } else {
                    return node;
                }
            }
        }

        // Returns the smallest key in the symbol table greater than or equal to key.
        public Key ceiling(Key key) {
            Node node = ceiling(root, key);
            if (node == null) {
                return null;
            }

            return node.key;
        }

        private Node ceiling(Node node, Key key) {
            if (node == null) {
                return null;
            }

            int compare = key.compareTo(node.key);

            if (compare == 0) {
                return node;
            } else if (compare > 0) {
                return ceiling(node.right, key);
            } else {
                Node leftNode = ceiling(node.left, key);
                if (leftNode != null) {
                    return leftNode;
                } else {
                    return node;
                }
            }
        }

        public Key select(int index) {
            if (index >= size()) {
                throw new IllegalArgumentException("Index is higher than tree size");
            }

            return select(root, index).key;
        }

        private Node select(Node node, int index) {
            int leftSubtreeSize = size(node.left);

            if (leftSubtreeSize == index) {
                return node;
            } else if (leftSubtreeSize > index) {
                return select(node.left, index);
            } else {
                return select(node.right, index - leftSubtreeSize - 1);
            }
        }

        public int rankFirst(Key key) {
            return rankFirst(root, key);
        }

        private int rankFirst(Node node, Key key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return rankFirst(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankFirst(node.right, key);
            } else {
                boolean hasDuplicateOnLeftSubtree = false;

                if (node.left != null && max(node.left).key.compareTo(key) == 0) {
                    hasDuplicateOnLeftSubtree = true;
                }

                if (hasDuplicateOnLeftSubtree) {
                    return rankFirst(node.left, key);
                } else {
                    return size(node.left);
                }
            }
        }

        public int rankLast(Key key) {
            return rankLast(root, key);
        }

        private int rankLast(Node node, Key key) {
            if (node == null) {
                return 0;
            }

            // Returns the number of keys less than node.key in the subtree rooted at node
            int compare = key.compareTo(node.key);
            if (compare < 0) {
                return rankLast(node.left, key);
            } else if (compare > 0) {
                return size(node.left) + 1 + rankLast(node.right, key);
            } else {
                boolean hasDuplicateOnRightSubtree = false;

                if (node.right != null && min(node.right).key.compareTo(key) == 0) {
                    hasDuplicateOnRightSubtree = true;
                }

                if (hasDuplicateOnRightSubtree) {
                    return size(node.left) + 1 + rankLast(node.right, key);
                } else {
                    return size(node.left);
                }
            }
        }

        // In the case of duplicates, return the rank of the rightmost key
        public int rank(Key key) {
            return rankLast(key);
        }

        // O(n lg n) since we are removing all duplicate min keys
        public void deleteMin() {
            if (isEmpty()) {
                return;
            }

            Key minKey = min();

            while (contains(minKey)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = deleteMin(root);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

        private Node deleteMin(Node node) {
            if (node.left == null) {
                return null;
            }

            if (!isRed(node.left) && !isRed(node.left.left)) {
                node = moveRedLeft(node);
            }

            node.left = deleteMin(node.left);
            return balance(node);
        }

        // O(n lg n) since we are removing all duplicate max keys
        public void deleteMax() {
            if (isEmpty()) {
                return;
            }

            Key maxKey = max();

            while (contains(maxKey)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = deleteMax(root);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

        private Node deleteMax(Node node) {
            if (isRed(node.left)) {
                node = rotateRight(node);
            }

            if (node.right == null) {
                return null;
            }

            if (!isRed(node.right) && !isRed(node.right.left)) {
                node = moveRedRight(node);
            }

            node.right = deleteMax(node.right);
            return balance(node);
        }

        // O(n lg n) since we are removing all duplicate keys
        public void delete(Key key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (isEmpty() || !contains(key)) {
                return;
            }

            while (contains(key)) {
                if (!isRed(root.left) && !isRed(root.right)) {
                    root.color = RED;
                }

                root = delete(root, key);

                if (!isEmpty()) {
                    root.color = BLACK;
                }
            }
        }

        private Node delete(Node node, Key key) {
            if (node == null) {
                return null;
            }

            if (key.compareTo(node.key) < 0) {
                if (!isRed(node.left) && node.left != null && !isRed(node.left.left)) {
                    node = moveRedLeft(node);
                }

                node.left = delete(node.left, key);
            } else {
                if (isRed(node.left)) {
                    node = rotateRight(node);
                }

                if (key.compareTo(node.key) == 0 && node.right == null) {
                    return null;
                }

                if (!isRed(node.right) && node.right != null && !isRed(node.right.left)) {
                    node = moveRedRight(node);
                }

                if (key.compareTo(node.key) == 0) {
                    Node aux = min(node.right);
                    node.key = aux.key;
                    node.value = aux.value;
                    node.right = deleteMin(node.right);
                } else {
                    node.right = delete(node.right, key);
                }
            }

            return balance(node);
        }

        private Node moveRedLeft(Node node) {
            // Assuming that node is red and both node.left and node.left.left are black,
            // make node.left or one of its children red
            flipColors(node);

            if (node.right != null && isRed(node.right.left)) {
                node.right = rotateRight(node.right);
                node = rotateLeft(node);
                flipColors(node);
            }

            return node;
        }

        private Node moveRedRight(Node node) {
            // Assuming that node is red and both node.right and node.right.left are black,
            // make node.right or one of its children red
            flipColors(node);

            if (node.left != null && isRed(node.left.left)) {
                node = rotateRight(node);
                flipColors(node);
            }

            return node;
        }

        private Node balance(Node node) {
            if (node == null) {
                return null;
            }

            if (isRed(node.right)) {
                node = rotateLeft(node);
            }

            if (isRed(node.left) && isRed(node.left.left)) {
                node = rotateRight(node);
            }

            if (isRed(node.left) && isRed(node.right)) {
                flipColors(node);
            }

            node.size = size(node.left) + 1 + size(node.right);

            return node;
        }

        public Iterable<Key> keys() {
            return keys(min(), max());
        }

        public Iterable<Key> keys(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to keys() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to keys() cannot be null");
            }

            Queue<Key> queue = new Queue<>();
            keys(root, queue, low, high);
            return queue;
        }

        private void keys(Node node, Queue<Key> queue, Key low, Key high) {
            if (node == null) {
                return;
            }

            int compareLow = low.compareTo(node.key);
            int compareHigh = high.compareTo(node.key);

            if (compareLow <= 0) {
                keys(node.left, queue, low, high);
            }

            if (compareLow <= 0 && compareHigh >= 0) {
                queue.enqueue(node.key);
            }

            if (compareHigh >= 0) {
                keys(node.right, queue, low, high);
            }
        }

        public int size(Key low, Key high) {
            if (low == null)  {
                throw new IllegalArgumentException("First argument to size() cannot be null");
            }
            if (high == null) {
                throw new IllegalArgumentException("Second argument to size() cannot be null");
            }

            if (low.compareTo(high) > 0) {
                return 0;
            }

            if (contains(high)) {
                return rankLast(high) - rankFirst(low) + 1;
            } else {
                return rankLast(high) - rankFirst(low);
            }
        }
    }

    public static void main(String[] args) {
        Exercise10 exercise10 = new Exercise10();
        RedBlackBSTDuplicateKeys<Integer, Integer> redBlackBSTDuplicateKeys = exercise10.new RedBlackBSTDuplicateKeys<>();

        // Test put()
        redBlackBSTDuplicateKeys.put(0, 0);
        redBlackBSTDuplicateKeys.put(0, 1);
        redBlackBSTDuplicateKeys.put(0, 2);
        redBlackBSTDuplicateKeys.put(0, 3);

        redBlackBSTDuplicateKeys.put(5, 7);
        redBlackBSTDuplicateKeys.put(5, 8);

        redBlackBSTDuplicateKeys.put(8, 9);
        redBlackBSTDuplicateKeys.put(8, 10);

        redBlackBSTDuplicateKeys.put(20, 11);
        redBlackBSTDuplicateKeys.put(20, 12);
        redBlackBSTDuplicateKeys.put(20, 13);
        redBlackBSTDuplicateKeys.put(20, 14);

        redBlackBSTDuplicateKeys.put(21, 15);
        redBlackBSTDuplicateKeys.put(22, 16);
        redBlackBSTDuplicateKeys.put(23, 17);
        redBlackBSTDuplicateKeys.put(24, 18);

        StdOut.println("Keys() test");
        for (Integer key : redBlackBSTDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }
        StdOut.println("\nExpected:");
        StdOut.println("Key 0: {0, 1, 2 or 3}");
        StdOut.println("Key 0: {0, 1, 2 or 3}");
        StdOut.println("Key 0: {0, 1, 2 or 3}");
        StdOut.println("Key 0: {0, 1, 2 or 3}");
        StdOut.println("Key 5: {7 or 8}");
        StdOut.println("Key 5: {7 or 8}");
        StdOut.println("Key 8: {9 or 10}");
        StdOut.println("Key 8: {9 or 10}");
        StdOut.println("Key 20: {11, 12, 13 or 14}");
        StdOut.println("Key 20: {11, 12, 13 or 14}");
        StdOut.println("Key 20: {11, 12, 13 or 14}");
        StdOut.println("Key 20: {11, 12, 13 or 14}");
        StdOut.println("Key 21: 15");
        StdOut.println("Key 22: 16");
        StdOut.println("Key 23: 17");
        StdOut.println("Key 24: 18");

        // Test size()
        StdOut.println("Keys size: " + redBlackBSTDuplicateKeys.size() + " Expected: 16");

        // Test size() with range
        StdOut.println("Keys size [0, 20]: " + redBlackBSTDuplicateKeys.size(0, 20) + " Expected: 12");

        // Test contains()
        StdOut.println("\nContains 8: " + redBlackBSTDuplicateKeys.contains(8) + " Expected: true");
        StdOut.println("Contains 9: " + redBlackBSTDuplicateKeys.contains(9) + " Expected: false");

        // Test min()
        StdOut.println("\nMin key: " + redBlackBSTDuplicateKeys.min() + " Expected: 0");

        // Test max()
        StdOut.println("Max key: " + redBlackBSTDuplicateKeys.max() + " Expected: 24");

        // Test floor()
        StdOut.println("Floor of 5: " + redBlackBSTDuplicateKeys.floor(5) + " Expected: 5");
        StdOut.println("Floor of 15: " + redBlackBSTDuplicateKeys.floor(15) + " Expected: 8");

        // Test ceiling()
        StdOut.println("Ceiling of 5: " + redBlackBSTDuplicateKeys.ceiling(5) + " Expected: 5");
        StdOut.println("Ceiling of 15: " + redBlackBSTDuplicateKeys.ceiling(15) + " Expected: 20");

        // Test select()
        StdOut.println("Select key of rank 3: " + redBlackBSTDuplicateKeys.select(3) + " Expected: 0");
        StdOut.println("Select key of rank 4: " + redBlackBSTDuplicateKeys.select(4) + " Expected: 5");

        // Test rank()
        // Note that the expected rank of key 8 is 7 and not 6, because we are assuming that rank returns the index
        // of the rightmost key when there are duplicates
        StdOut.println("Rank of key 8: " + redBlackBSTDuplicateKeys.rank(8) + " Expected: 7");
        StdOut.println("Rank of key 9: " + redBlackBSTDuplicateKeys.rank(9) + " Expected: 8");

        // Test delete()
        StdOut.println("\nDelete key 20");
        redBlackBSTDuplicateKeys.delete(20);

        for (Integer key : redBlackBSTDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + redBlackBSTDuplicateKeys.size() + " Expected: 12");

        StdOut.println("\nDelete key 5");
        redBlackBSTDuplicateKeys.delete(5);

        for (Integer key : redBlackBSTDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + redBlackBSTDuplicateKeys.size() + " Expected: 10");

        // Test deleteMin()
        StdOut.println("\nDelete min (key 0)");
        redBlackBSTDuplicateKeys.deleteMin();

        for (Integer key : redBlackBSTDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + redBlackBSTDuplicateKeys.size() + " Expected: 6");

        // Test deleteMax()
        StdOut.println("\nDelete max (key 24)");
        redBlackBSTDuplicateKeys.deleteMax();

        for (Integer key : redBlackBSTDuplicateKeys.keys()) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }
        StdOut.println("Keys size: " + redBlackBSTDuplicateKeys.size() + " Expected: 5");

        // Test keys() with range
        StdOut.println("\nKeys in range [2, 10]");
        for (Integer key : redBlackBSTDuplicateKeys.keys(2, 10)) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }

        StdOut.println("\nKeys in range [20, 22]");
        for (Integer key : redBlackBSTDuplicateKeys.keys(20, 22)) {
            StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
        }

        // Delete all
        StdOut.println("\nDelete all");
        while (redBlackBSTDuplicateKeys.size() > 0) {
            for (Integer key : redBlackBSTDuplicateKeys.keys()) {
                StdOut.println("Key " + key + ": " + redBlackBSTDuplicateKeys.get(key));
            }
            // redBlackBSTDuplicateKeys.delete(redBlackBSTDuplicateKeys.select(0));
            redBlackBSTDuplicateKeys.delete(redBlackBSTDuplicateKeys.select(redBlackBSTDuplicateKeys.size() - 1));
            StdOut.println();
        }
    }
}