Question: Size. Implement very eager size() (that keeps in each node the number of keys in its subtree) for TrieST and TST.

Answer:
package chapter5.section2;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 22/01/18.
 */
public class Exercise10_Size {

    @SuppressWarnings("unchecked")
    public static class TrieWithSize<Value> extends Trie<Value> {

        private NodeWithSize root = new NodeWithSize();

        private static class NodeWithSize extends Node {
            private int size;
            private NodeWithSize[] next = new NodeWithSize[R];
        }

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

        private int size(NodeWithSize nodeWithSize) {
            if (nodeWithSize == null) {
                return 0;
            }

            return nodeWithSize.size;
        }

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

            return get(key) != null;
        }

        @Override
        public Value get(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (key.length() == 0) {
                throw new IllegalArgumentException("Key must have a positive length");
            }

            Node node = get(root, key, 0);

            if (node == null) {
                return null;
            }
            return (Value) node.value;
        }

        private Node get(NodeWithSize node, String key, int digit) {
            if (node == null) {
                return null;
            }

            if (digit == key.length()) {
                return node;
            }

            char nextChar = key.charAt(digit); // Use digitTh key char to identify subtrie.
            return get(node.next[nextChar], key, digit + 1);
        }

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

            boolean isNewKey = false;

            if (!contains(key)) {
                isNewKey = true;
            }

            if (value == null) {
                delete(key);
            } else {
                root = put(root, key, value, 0, isNewKey);
            }
        }

        private NodeWithSize put(NodeWithSize node, String key, Value value, int digit, boolean isNewKey) {
            if (node == null) {
                node = new NodeWithSize();
            }

            if (isNewKey) {
                node.size = node.size + 1;
            }

            if (digit == key.length()) {
                node.value = value;
                return node;
            }

            char nextChar = key.charAt(digit); // Use digitTh key char to identify subtrie.
            node.next[nextChar] = put(node.next[nextChar], key, value, digit + 1, isNewKey);

            return node;
        }

        @Override
        public void delete(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

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

            root = delete(root, key, 0);
        }

        private NodeWithSize delete(NodeWithSize node, String key, int digit) {
            if (node == null) {
                return null;
            }

            node.size = node.size - 1;

            if (digit == key.length()) {
                node.value = null;
            } else {
                char nextChar = key.charAt(digit);
                node.next[nextChar] = delete(node.next[nextChar], key, digit + 1);
            }

            if (node.value != null) {
                return node;
            }

            for (char nextChar = 0; nextChar < R; nextChar++) {
                if (node.next[nextChar] != null) {
                    return node;
                }
            }

            return null;
        }
    }

    public static class TernarySearchTrieWithSize<Value> extends TernarySearchTrie<Value> {

        private int size;
        private NodeWithSize root;

        public class NodeWithSize extends Node {
            private int size;

            private NodeWithSize left;
            private NodeWithSize middle;
            private NodeWithSize right;
        }

        public int size() {
            return size;
        }

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

            return get(key) != null;
        }

        @Override
        public Value get(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (key.length() == 0) {
                throw new IllegalArgumentException("Key must have a positive length");
            }

            NodeWithSize node = get(root, key, 0);

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

        private NodeWithSize get(NodeWithSize node, String key, int digit) {
            if (node == null) {
                return null;
            }

            char currentChar = key.charAt(digit);

            if (currentChar < node.character) {
                return get(node.left, key, digit);
            } else if (currentChar > node.character) {
                return get(node.right, key, digit);
            } else if (digit < key.length() - 1) {
                return get(node.middle, key, digit + 1);
            } else {
                return node;
            }
        }

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

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

            boolean isNewKey = false;

            if (!contains(key)) {
                isNewKey = true;
                size++;
            }

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

        private NodeWithSize put(NodeWithSize node, String key, Value value, int digit, boolean isNewKey) {
            char currentChar = key.charAt(digit);

            if (node == null) {
                node = new NodeWithSize();
                node.character = currentChar;
            }

            if (currentChar < node.character) {
                node.left = put(node.left, key, value, digit, isNewKey);
            } else if (currentChar > node.character) {
                node.right = put(node.right, key, value, digit, isNewKey);
            } else if (digit < key.length() - 1) {
                node.middle = put(node.middle, key, value, digit + 1, isNewKey);

                if (isNewKey) {
                    node.size = node.size + 1;
                }
            } else {
                node.value = value;

                if (isNewKey) {
                    node.size = node.size + 1;
                }
            }

            return node;
        }

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

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

            root = delete(root, key, 0);
            size--;
        }

        private NodeWithSize delete(NodeWithSize node, String key, int digit) {
            if (node == null) {
                return null;
            }

            if (digit == key.length() - 1) {
                node.size = node.size - 1;
                node.value = null;
            } else {
                char nextChar = key.charAt(digit);

                if (nextChar < node.character) {
                    node.left = delete(node.left, key, digit);
                } else if (nextChar > node.character) {
                    node.right = delete(node.right, key, digit);
                } else {
                    node.size = node.size - 1;
                    node.middle = delete(node.middle, key, digit + 1);
                }
            }

            if (node.size == 0) {
                if (node.left == null && node.right == null) {
                    return null;
                } else if (node.left == null) {
                    return node.right;
                } else if (node.right == null) {
                    return node.left;
                } else {
                    NodeWithSize aux = node;
                    node = min(aux.right);
                    node.right = deleteMin(aux.right);
                    node.left = aux.left;
                }
            }

            return node;
        }

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

            return min(node.left);
        }

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

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

    // Considering ' ' as root node when printing
    private void printNodeSizes(TrieWithSize.NodeWithSize currentNode, char currentChar) {
        if (currentNode == null) {
            return;
        }

        StdOut.println("Node character: " + currentChar + " Size: " + currentNode.size);

        for (char nextChar = 0; nextChar < TrieWithSize.R; nextChar++) {
            if (currentNode.next[nextChar] != null) {
                printNodeSizes(currentNode.next[nextChar], nextChar);
            }
        }
    }

    private void printNodeSizes(TernarySearchTrieWithSize.NodeWithSize currentNode) {
        if (currentNode == null) {
            return;
        }

        printNodeSizes(currentNode.left);

        StdOut.println("Node character: " + currentNode.character + " Size: " + currentNode.size);
        printNodeSizes(currentNode.middle);
        printNodeSizes(currentNode.right);
    }

    public static void main(String[] args) {
        Exercise10_Size exercise10_size = new Exercise10_Size();

        StdOut.println("********Trie tests********");
        TrieWithSize<Integer> trieWithSize = new TrieWithSize<>();

        trieWithSize.put("Rene", 0);
        trieWithSize.put("Re", 1);
        trieWithSize.put("Algorithms", 2);
        trieWithSize.put("Algo", 3);
        trieWithSize.put("Algor", 4);
        trieWithSize.put("Tree", 5);
        trieWithSize.put("Trie", 6);
        trieWithSize.put("TST", 7);

        StdOut.println("Size of Trie: " + trieWithSize.size());
        StdOut.println("Expected: 8");

        StdOut.println();
        exercise10_size.printNodeSizes(trieWithSize.root, ' ');

        StdOut.println("\nExpected:\n" +
                "Node character:   Size: 8\n" +
                "Node character: A Size: 3\n" +
                "Node character: l Size: 3\n" +
                "Node character: g Size: 3\n" +
                "Node character: o Size: 3\n" +
                "Node character: r Size: 2\n" +
                "Node character: i Size: 1\n" +
                "Node character: t Size: 1\n" +
                "Node character: h Size: 1\n" +
                "Node character: m Size: 1\n" +
                "Node character: s Size: 1\n" +
                "Node character: R Size: 2\n" +
                "Node character: e Size: 2\n" +
                "Node character: n Size: 1\n" +
                "Node character: e Size: 1\n" +
                "Node character: T Size: 3\n" +
                "Node character: S Size: 1\n" +
                "Node character: T Size: 1\n" +
                "Node character: r Size: 2\n" +
                "Node character: e Size: 1\n" +
                "Node character: e Size: 1\n" +
                "Node character: i Size: 1\n" +
                "Node character: e Size: 1");

        StdOut.println("\nDeleted Trie key");
        trieWithSize.delete("Trie");

        exercise10_size.printNodeSizes(trieWithSize.root, ' ');
        StdOut.println("Expected: no 'i' and 'e' nodes after 'Tr'");

        StdOut.println("\nSize of Trie: " + trieWithSize.size());
        StdOut.println("Expected: 7");

        StdOut.println("\nDeleted Re key");
        trieWithSize.delete("Re");

        exercise10_size.printNodeSizes(trieWithSize.root, ' ');
        StdOut.println("Expected: 'R' and 'e' with size 1");

        StdOut.println("\nSize of Trie: " + trieWithSize.size());
        StdOut.println("Expected: 6");

        StdOut.println("\nDeleted Algo key");
        trieWithSize.delete("Algo");

        exercise10_size.printNodeSizes(trieWithSize.root, ' ');
        StdOut.println("Expected: 'A', 'l', 'g', 'o' and 'r' nodes with size 2");

        StdOut.println("\nSize of Trie: " + trieWithSize.size());
        StdOut.println("Expected: 5");

        StdOut.println("\n********Ternary Search Trie tests********");
        TernarySearchTrieWithSize<Integer> tstWithSize = new TernarySearchTrieWithSize<>();

        tstWithSize.put("Rene", 0);
        tstWithSize.put("Re", 1);
        tstWithSize.put("Algorithms", 2);
        tstWithSize.put("Algo", 3);
        tstWithSize.put("Algor", 4);
        tstWithSize.put("Tree", 5);
        tstWithSize.put("Trie", 6);
        tstWithSize.put("TST", 7);

        StdOut.println("Size of TST: " + tstWithSize.size());
        StdOut.println("Expected: 8");

        StdOut.println();
        exercise10_size.printNodeSizes(tstWithSize.root);

        StdOut.println("\nExpected:\n" +
                "Node character: A Size: 3\n" +
                "Node character: l Size: 3\n" +
                "Node character: g Size: 3\n" +
                "Node character: o Size: 3\n" +
                "Node character: r Size: 2\n" +
                "Node character: i Size: 1\n" +
                "Node character: t Size: 1\n" +
                "Node character: h Size: 1\n" +
                "Node character: m Size: 1\n" +
                "Node character: s Size: 1\n" +
                "Node character: R Size: 2\n" +
                "Node character: e Size: 2\n" +
                "Node character: n Size: 1\n" +
                "Node character: e Size: 1\n" +
                "Node character: T Size: 3\n" +
                "Node character: S Size: 1\n" +
                "Node character: T Size: 1\n" +
                "Node character: r Size: 2\n" +
                "Node character: e Size: 1\n" +
                "Node character: e Size: 1\n" +
                "Node character: i Size: 1\n" +
                "Node character: e Size: 1");

        StdOut.println("\nDeleted Trie key");
        tstWithSize.delete("Trie");

        exercise10_size.printNodeSizes(tstWithSize.root);
        StdOut.println("Expected: no 'i' and 'e' nodes after 'Tr'");

        StdOut.println("\nSize of TST: " + tstWithSize.size());
        StdOut.println("Expected: 7");

        StdOut.println("\nDeleted Re key");
        tstWithSize.delete("Re");

        exercise10_size.printNodeSizes(tstWithSize.root);
        StdOut.println("Expected: 'R' and 'e' with size 1");

        StdOut.println("\nSize of TST: " + tstWithSize.size());
        StdOut.println("Expected: 6");

        StdOut.println("\nDeleted Algo key");
        tstWithSize.delete("Algo");

        exercise10_size.printNodeSizes(tstWithSize.root);
        StdOut.println("Expected: 'A', 'l', 'g', 'o' and 'r' nodes with size 2");

        StdOut.println("\nSize of TST: " + tstWithSize.size());
        StdOut.println("Expected: 5");
    }
}


Question: Hybrid TST with R2-way branching at the root. Add code to TST to do multiway branching at the first two levels, as described in the text.

Answer:
package chapter5.section2;

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

import java.util.StringJoiner;

/**
 * Created by Rene Argento on 05/02/18.
 */
public class Exercise13_HybridTSTWithR2BranchingAtTheRoot {

    @SuppressWarnings("unchecked")
    public class HybridTernarySearchTrie<Value> {

        private static final int R = 256;
        private int size;
        private TernarySearchTrie<Value>[][] ternarySearchTries;
        private static final int NULL_CHAR_INDEX = R;

        public HybridTernarySearchTrie() {
            // Columns have size R + 1 because there may be keys of length 1, which are equivalent to
            // row = character1, column = R
            ternarySearchTries = new TernarySearchTrie[R][R + 1];

            for (int tst1 = 0; tst1 < R; tst1++) {
                for (int tst2 = 0; tst2 <= R; tst2++) {
                    ternarySearchTries[tst1][tst2] = new TernarySearchTrie<>();
                }
            }
        }

        private TernarySearchTrie<Value> getTernarySearchTrie(String key) {
            TernarySearchTrie<Value> ternarySearchTrie;

            char character1 = key.charAt(0);

            if (key.length() == 1) {
                ternarySearchTrie = ternarySearchTries[character1][NULL_CHAR_INDEX];
            } else {
                char character2 = key.charAt(1);
                ternarySearchTrie = ternarySearchTries[character1][character2];
            }

            return ternarySearchTrie;
        }

        public int size() {
            return size;
        }

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

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

            return get(key) != null;
        }

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

            if (key.length() == 0) {
                throw new IllegalArgumentException("Key must have a positive length");
            }

            TernarySearchTrie<Value> ternarySearchTrie = getTernarySearchTrie(key);
            return ternarySearchTrie.get(key);
        }

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

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

            if (!contains(key)) {
                size++;
            }

            TernarySearchTrie<Value> ternarySearchTrie = getTernarySearchTrie(key);
            ternarySearchTrie.put(key, value);
        }

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

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

            TernarySearchTrie<Value> ternarySearchTrie = getTernarySearchTrie(key);
            ternarySearchTrie.delete(key);
            size--;
        }

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

            for (char tst1 = 0; tst1 < R; tst1++) {

                // First add key of size 1
                getAllKeysInTST(tst1, NULL_CHAR_INDEX, keys);

                for (char tst2 = 0; tst2 < R; tst2++) {
                    getAllKeysInTST(tst1, tst2, keys);
                }
            }

            return keys;
        }

        private void getAllKeysInTST(int index1, int index2, Queue<String> keys) {

            if (ternarySearchTries[index1][index2].size() > 0) {
                for (String key : ternarySearchTries[index1][index2].keys()) {
                    keys.enqueue(key);
                }
            }
        }

        public Iterable<String> keysWithPrefix(String prefix) {
            if (prefix == null) {
                throw new IllegalArgumentException("Prefix cannot be null");
            }

            Queue<String> keysWithPrefix = new Queue<>();

            if (prefix.length() == 1) {
                char character1 = prefix.charAt(0);

                // Check if key of size 1 is in the hybrid ternary search trie
                getAllKeysInTST(character1, NULL_CHAR_INDEX, keysWithPrefix);

                // Also check for keys of size 2 or higher
                for (char tst = 0; tst < R; tst++) {
                    getAllKeysInTST(character1, tst, keysWithPrefix);
                }
            } else if (prefix.length() > 1) {
                char character1 = prefix.charAt(0);
                char character2 = prefix.charAt(1);

                getAllKeysInTST(character1, character2, keysWithPrefix);
            }

            return keysWithPrefix;
        }

        public Iterable<String> keysThatMatch(String pattern) {
            if (pattern == null) {
                throw new IllegalArgumentException("Pattern cannot be null");
            }

            Queue<String> keysThatMatch = new Queue<>();

            if (pattern.length() == 1) {
                char character = pattern.charAt(0);

                if (character != '.') {
                    getAllKeysInTST(character, NULL_CHAR_INDEX, keysThatMatch);
                } else {
                    for (char tst = 0; tst < R; tst++) {
                        getAllKeysInTST(tst, NULL_CHAR_INDEX, keysThatMatch);
                    }
                }
            } else if (pattern.length() > 1) {
                char character1 = pattern.charAt(0);
                char character2 = pattern.charAt(1);

                if (character1 != '.' && character2 != '.') {
                    TernarySearchTrie<Value> ternarySearchTrie = getTernarySearchTrie(pattern);

                    for (String key : ternarySearchTrie.keysThatMatch(pattern)) {
                        keysThatMatch.enqueue(key);
                    }
                } else if (character1 == '.' && character2 != '.') {

                    for (char tst = 0; tst < R; tst++) {
                        TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst][character2];

                        for (String key : ternarySearchTrie.keysThatMatch(pattern)) {
                            keysThatMatch.enqueue(key);
                        }
                    }
                } else if (character1 != '.' && character2 == '.') {

                    for (char tst = 0; tst < R; tst++) {
                        TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[character1][tst];

                        for (String key : ternarySearchTrie.keysThatMatch(pattern)) {
                            keysThatMatch.enqueue(key);
                        }
                    }
                } else {

                    for (char tst1 = 0; tst1 < R; tst1++) {
                        for (int tst2 = 0; tst2 < R; tst2++) {
                            TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];

                            for (String key : ternarySearchTrie.keysThatMatch(pattern)) {
                                keysThatMatch.enqueue(key);
                            }
                        }
                    }
                }
            }

            return keysThatMatch;
        }

        public String longestPrefixOf(String query) {
            if (query == null) {
                throw new IllegalArgumentException("Query cannot be null");
            }

            TernarySearchTrie<Value> ternarySearchTrie = getTernarySearchTrie(query);
            return ternarySearchTrie.longestPrefixOf(query);
        }

        public String floor(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (key.length() == 0) {
                return key;
            }

            String floorKey;

            char character1 = key.charAt(0);
            char character2;

            if (key.length() == 1) {
                character2 = NULL_CHAR_INDEX;
            } else {
                character2 = key.charAt(1);
            }

            boolean mustBeEqualDigit = true;

            for (char tst1 = character1; true; tst1--) {

                if (!mustBeEqualDigit) {
                    character2 = R - 1;
                }

                for (char tst2 = character2; true; tst2--) {
                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];
                    floorKey = ternarySearchTrie.floor(key);

                    if (floorKey != null) {
                        break;
                    }

                    // Before decrementing character1 value check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];
                        floorKey = ternarySearchTrieKeySize1.floor(key);

                        mustBeEqualDigit = false;
                        break;
                    }
                }

                // tst1 value never becomes less than zero in the for loop, so we need this extra validation
                if (tst1 == 0) {
                    break;
                }

                if (floorKey != null) {
                    break;
                }
            }

            return floorKey;
        }

        public String ceiling(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (key.length() == 0) {
                return key;
            }

            String ceilingKey = null;

            char character1 = key.charAt(0);
            char character2;

            if (key.length() == 1) {
                character2 = NULL_CHAR_INDEX;
            } else {
                character2 = key.charAt(1);
            }

            boolean mustBeEqualDigit = true;

            for (char tst1 = character1; tst1 < R; tst1++) {

                if (!mustBeEqualDigit) {
                    character2 = 0;
                }

                for (char tst2 = character2; tst2 < R; tst2++) {

                    // Before beginning to check character2 value, check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];
                        ceilingKey = ternarySearchTrieKeySize1.ceiling(key);

                        if (ceilingKey != null) {
                            break;
                        }
                    }

                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];
                    ceilingKey = ternarySearchTrie.ceiling(key);

                    if (ceilingKey != null) {
                        break;
                    }

                    if (tst2 == R - 1) {
                        mustBeEqualDigit = false;
                    }
                }

                if (ceilingKey != null) {
                    break;
                }
            }

            return ceilingKey;
        }

        public String select(int index) {
            if (index < 0 || index >= size()) {
                throw new IllegalArgumentException("Index cannot be negative and must be lower than TST size");
            }

            String targetKey = null;
            int currentIndex = 0;

            for (char tst1 = 0; tst1 < R; tst1++) {
                for (char tst2 = 0; tst2 < R; tst2++) {

                    // Before beginning to check character2 value, check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];

                        int size = ternarySearchTrieKeySize1.size();

                        if (currentIndex + size > index) {
                            int targetIndex = index - currentIndex;
                            targetKey = ternarySearchTrieKeySize1.select(targetIndex);
                            break;
                        }

                        currentIndex += size;
                    }

                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];

                    int size = ternarySearchTrie.size();

                    if (currentIndex + size > index) {
                        int targetIndex = index - currentIndex;
                        targetKey = ternarySearchTrie.select(targetIndex);
                        break;
                    }

                    currentIndex += size;
                }

                if (targetKey != null) {
                    break;
                }
            }

            return targetKey;
        }

        public int rank(String key) {
            if (key == null) {
                throw new IllegalArgumentException("Key cannot be null");
            }

            if (key.length() == 0) {
                return 0;
            }

            char character1 = key.charAt(0);
            char character2;

            TernarySearchTrie<Value> keyTernarySearchTrie;
            boolean canDecreaseCharacterValue = true;

            if (key.length() == 1) {
                character2 = NULL_CHAR_INDEX;

                keyTernarySearchTrie = ternarySearchTries[character1][character2];

                if (character1 > 0) {
                    character1--;
                } else {
                    canDecreaseCharacterValue = false;
                }
            } else {
                character2 = key.charAt(1);

                keyTernarySearchTrie = ternarySearchTries[character1][character2];

                if (character2 > 0) {
                    character2--;
                } else if (character1 > 0) {
                    character1--;
                    character2 = R - 1;
                } else {
                    canDecreaseCharacterValue = false;
                }
            }

            // If there are no keys with a prefix lower than the current key we just have to check its TST
            if (!canDecreaseCharacterValue) {
                return keyTernarySearchTrie.rank(key);
            }

            int totalSize = 0;
            boolean mustBeEqualDigit = true;

            for (char tst1 = character1; true; tst1--) {

                if (!mustBeEqualDigit) {
                    character2 = R - 1;
                }

                for (char tst2 = character2; true; tst2--) {
                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];
                    totalSize += ternarySearchTrie.size();

                    // Before decrementing character1 value check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];
                        totalSize += ternarySearchTrieKeySize1.size();

                        mustBeEqualDigit = false;
                        break;
                    }
                }

                // tst1 value never becomes less than zero in the for loop, so we need this extra validation
                if (tst1 == 0) {
                    break;
                }
            }

            return totalSize + keyTernarySearchTrie.rank(key);
        }

        public String min() {
            if (isEmpty()) {
                return null;
            }

            for (char tst1 = 0; tst1 < R; tst1++) {
                for (char tst2 = 0; tst2 < R; tst2++) {

                    // Before beginning to check character2 value, check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];
                        if (!ternarySearchTrieKeySize1.isEmpty()) {
                            return ternarySearchTrieKeySize1.min();
                        }
                    }

                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];
                    if (!ternarySearchTrie.isEmpty()) {
                        return ternarySearchTrie.min();
                    }
                }
            }

            return null;
        }

        public String max() {
            if (isEmpty()) {
                return null;
            }

            for (char tst1 = R - 1; true; tst1--) {
                for (char tst2 = R - 1; true; tst2--) {

                    TernarySearchTrie<Value> ternarySearchTrie = ternarySearchTries[tst1][tst2];
                    if (!ternarySearchTrie.isEmpty()) {
                        return ternarySearchTrie.max();
                    }

                    // Before decrementing character1 value check for key of size 1
                    if (tst2 == 0) {
                        TernarySearchTrie<Value> ternarySearchTrieKeySize1 = ternarySearchTries[tst1][NULL_CHAR_INDEX];
                        if (!ternarySearchTrieKeySize1.isEmpty()) {
                            return ternarySearchTrieKeySize1.max();
                        }

                        break;
                    }
                }

                // tst1 value never becomes less than zero in the for loop, so we need this extra validation
                if (tst1 == 0) {
                    break;
                }
            }

            return null;
        }

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

            String minKey = min();
            delete(minKey);
        }

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

            String maxKey = max();
            delete(maxKey);
        }
    }

    public static void main(String[] args) {
        HybridTernarySearchTrie<Integer> hybridTernarySearchTrie =
                new Exercise13_HybridTSTWithR2BranchingAtTheRoot().new HybridTernarySearchTrie<>();

        StdOut.println("Size: " + hybridTernarySearchTrie.size());
        StdOut.println("Expected: 0");

        // Put tests
        hybridTernarySearchTrie.put("Rene", 0);
        hybridTernarySearchTrie.put("Re", 1);
        hybridTernarySearchTrie.put("Re", 10);
        hybridTernarySearchTrie.put("Algorithms", 2);
        hybridTernarySearchTrie.put("Algo", 3);
        hybridTernarySearchTrie.put("Algor", 4);
        hybridTernarySearchTrie.put("Tree", 5);
        hybridTernarySearchTrie.put("Trie", 6);
        hybridTernarySearchTrie.put("TST", 7);
        hybridTernarySearchTrie.put("Trie123", 8);
        hybridTernarySearchTrie.put("Z-Function", 9);
        hybridTernarySearchTrie.put("B", 11);
        hybridTernarySearchTrie.put("Binary", 12);

        StdOut.println("\nSize: " + hybridTernarySearchTrie.size());
        StdOut.println("Expected: 12");

        // Get tests
        StdOut.println("\nGet Re: " + hybridTernarySearchTrie.get("Re"));
        StdOut.println("Expected: 10");
        StdOut.println("Get Algorithms: " + hybridTernarySearchTrie.get("Algorithms"));
        StdOut.println("Expected: 2");
        StdOut.println("Get Trie123: " + hybridTernarySearchTrie.get("Trie123"));
        StdOut.println("Expected: 8");
        StdOut.println("Get Algori: " + hybridTernarySearchTrie.get("Algori"));
        StdOut.println("Expected: null");
        StdOut.println("Get Zooom: " + hybridTernarySearchTrie.get("Zooom"));
        StdOut.println("Expected: null");

        // Keys() test
        StdOut.println("\nAll keys");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        // Keys with prefix tests
        StdOut.println("\nKeys with prefix Alg");
        StringJoiner keysWithPrefix1 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysWithPrefix("Alg")) {
            keysWithPrefix1.add(key);
        }
        StdOut.println(keysWithPrefix1.toString());
        StdOut.println("Expected: Algo Algor Algorithms");

        StdOut.println("\nKeys with prefix T");
        StringJoiner keysWithPrefix2 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysWithPrefix("T")) {
            keysWithPrefix2.add(key);
        }
        StdOut.println(keysWithPrefix2.toString());
        StdOut.println("Expected: TST Tree Trie Trie123");

        StdOut.println("\nKeys with prefix R");
        StringJoiner keysWithPrefix3 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysWithPrefix("R")) {
            keysWithPrefix3.add(key);
        }
        StdOut.println(keysWithPrefix3.toString());
        StdOut.println("Expected: Re Rene");

        StdOut.println("\nKeys with prefix ZZZ");
        StringJoiner keysWithPrefix4 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysWithPrefix("ZZZ")) {
            keysWithPrefix4.add(key);
        }
        StdOut.println(keysWithPrefix4.toString());
        StdOut.println("Expected: ");

        StdOut.println("\nKeys with prefix B");
        StringJoiner keysWithPrefix5 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysWithPrefix("B")) {
            keysWithPrefix5.add(key);
        }
        StdOut.println(keysWithPrefix5.toString());
        StdOut.println("Expected: B Binary");

        // Keys that match tests
        StdOut.println("\nKeys that match Alg..");
        StringJoiner keysThatMatch1 = new StringJoiner("Alg..");

        for (String key : hybridTernarySearchTrie.keysThatMatch("Alg..")) {
            keysThatMatch1.add(key);
        }
        StdOut.println(keysThatMatch1.toString());
        StdOut.println("Expected: Algor");

        StdOut.println("\nKeys that match Re");
        StringJoiner keysThatMatch2 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysThatMatch("Re")) {
            keysThatMatch2.add(key);
        }
        StdOut.println(keysThatMatch2.toString());
        StdOut.println("Expected: Re");

        StdOut.println("\nKeys that match Tr.e");
        StringJoiner keysThatMatch3 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysThatMatch("Tr.e")) {
            keysThatMatch3.add(key);
        }
        StdOut.println(keysThatMatch3.toString());
        StdOut.println("Expected: Tree Trie");

        StdOut.println("\nKeys that match .");
        StringJoiner keysThatMatch4 = new StringJoiner(" ");

        for (String key : hybridTernarySearchTrie.keysThatMatch(".")) {
            keysThatMatch4.add(key);
        }
        StdOut.println(keysThatMatch4.toString());
        StdOut.println("Expected: B");

        // Longest-prefix-of tests
        StdOut.println("\nLongest prefix of Re: " + hybridTernarySearchTrie.longestPrefixOf("Re"));
        StdOut.println("Expected: Re");

        StdOut.println("Longest prefix of Alg: " + hybridTernarySearchTrie.longestPrefixOf("Alg"));
        StdOut.println("Expected: ");

        StdOut.println("Longest prefix of Algori: " + hybridTernarySearchTrie.longestPrefixOf("Algori"));
        StdOut.println("Expected: Algor");

        StdOut.println("Longest prefix of Trie12345: " + hybridTernarySearchTrie.longestPrefixOf("Trie12345"));
        StdOut.println("Expected: Trie123");

        StdOut.println("Longest prefix of Zooom: " + hybridTernarySearchTrie.longestPrefixOf("Zooom"));
        StdOut.println("Expected: ");

        // Min tests
        StdOut.println("\nMin key: " + hybridTernarySearchTrie.min());
        StdOut.println("Expected: Algo");

        // Max tests
        StdOut.println("\nMax key: " + hybridTernarySearchTrie.max());
        StdOut.println("Expected: Z-Function");

        // Delete min and delete max tests
        hybridTernarySearchTrie.put("ABCKey", 11);
        hybridTernarySearchTrie.put("ZKey", 12);

        StdOut.println("\nKeys after ABCKey and ZKey insert: ");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        hybridTernarySearchTrie.deleteMin();

        StdOut.println("\nKeys after deleteMin: ");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        StdOut.println("Expected: ABCKey deleted");

        hybridTernarySearchTrie.deleteMax();

        StdOut.println("\nKeys after deleteMax: ");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        StdOut.println("Expected: ZKey deleted");

        // Floor tests
        StdOut.println("\nFloor of Re: " + hybridTernarySearchTrie.floor("Re"));
        StdOut.println("Expected: Re");
        StdOut.println("Floor of Algori: " + hybridTernarySearchTrie.floor("Algori"));
        StdOut.println("Expected: Algor");
        StdOut.println("Floor of Azure: " + hybridTernarySearchTrie.floor("Azure"));
        StdOut.println("Expected: Algorithms");
        StdOut.println("Floor of Tarjan: " + hybridTernarySearchTrie.floor("Tarjan"));
        StdOut.println("Expected: TST");
        StdOut.println("Floor of AA: " + hybridTernarySearchTrie.floor("AA"));
        StdOut.println("Expected: null");
        StdOut.println("Floor of Zoom: " + hybridTernarySearchTrie.floor("Zoom"));
        StdOut.println("Expected: Z-Function");
        StdOut.println("Floor of TAB: " + hybridTernarySearchTrie.floor("TAB"));
        StdOut.println("Expected: Rene");
        StdOut.println("Floor of Cat: " + hybridTernarySearchTrie.floor("Cat"));
        StdOut.println("Expected: Binary");
        StdOut.println("Floor of Ball: " + hybridTernarySearchTrie.floor("Ball"));
        StdOut.println("Expected: B");

        // Ceiling tests
        StdOut.println("\nCeiling of Re: " + hybridTernarySearchTrie.ceiling("Re"));
        StdOut.println("Expected: Re");
        StdOut.println("Ceiling of Algori: " + hybridTernarySearchTrie.ceiling("Algori"));
        StdOut.println("Expected: Algorithms");
        StdOut.println("Ceiling of Bull: " + hybridTernarySearchTrie.ceiling("Bull"));
        StdOut.println("Expected: Re");
        StdOut.println("Ceiling of Tarjan: " + hybridTernarySearchTrie.ceiling("Tarjan"));
        StdOut.println("Expected: Tree");
        StdOut.println("Ceiling of AA: " + hybridTernarySearchTrie.ceiling("AA"));
        StdOut.println("Expected: Algo");
        StdOut.println("Ceiling of Zoom: " + hybridTernarySearchTrie.ceiling("Zoom"));
        StdOut.println("Expected: null");
        StdOut.println("Ceiling of Tro: " + hybridTernarySearchTrie.ceiling("Tro"));
        StdOut.println("Expected: Z-Function");
        StdOut.println("Ceiling of Ruby: " + hybridTernarySearchTrie.ceiling("Ruby"));
        StdOut.println("Expected: TST");
        StdOut.println("Ceiling of Azure: " + hybridTernarySearchTrie.ceiling("Azure"));
        StdOut.println("Expected: B");
        StdOut.println("Ceiling of Ball: " + hybridTernarySearchTrie.ceiling("Ball"));
        StdOut.println("Expected: Binary");

        // Select tests
        StdOut.println("\nSelect 0: " + hybridTernarySearchTrie.select(0));
        StdOut.println("Expected: Algo");
        StdOut.println("Select 3: " + hybridTernarySearchTrie.select(3));
        StdOut.println("Expected: B");
        StdOut.println("Select 5: " + hybridTernarySearchTrie.select(5));
        StdOut.println("Expected: Re");
        StdOut.println("Select 2: " + hybridTernarySearchTrie.select(2));
        StdOut.println("Expected: Algorithms");
        StdOut.println("Select 7: " + hybridTernarySearchTrie.select(7));
        StdOut.println("Expected: TST");
        StdOut.println("Select 10: " + hybridTernarySearchTrie.select(10));
        StdOut.println("Expected: Trie123");
        StdOut.println("Select 11: " + hybridTernarySearchTrie.select(11));
        StdOut.println("Expected: Z-Function");

        // Rank tests
        StdOut.println("\nRank of R: " + hybridTernarySearchTrie.rank("R"));
        StdOut.println("Expected: 5");
        StdOut.println("Rank of Re: " + hybridTernarySearchTrie.rank("Re"));
        StdOut.println("Expected: 5");
        StdOut.println("Rank of A: " + hybridTernarySearchTrie.rank("A"));
        StdOut.println("Expected: 0");
        StdOut.println("Rank of Algo: " + hybridTernarySearchTrie.rank("Algo"));
        StdOut.println("Expected: 0");
        StdOut.println("Rank of Algori: " + hybridTernarySearchTrie.rank("Algori"));
        StdOut.println("Expected: 2");
        StdOut.println("Rank of Algorithms: " + hybridTernarySearchTrie.rank("Algorithms"));
        StdOut.println("Expected: 2");
        StdOut.println("Rank of B: " + hybridTernarySearchTrie.rank("B"));
        StdOut.println("Expected: 3");
        StdOut.println("Rank of Ball: " + hybridTernarySearchTrie.rank("Ball"));
        StdOut.println("Expected: 4");
        StdOut.println("Rank of Tarjan: " + hybridTernarySearchTrie.rank("Tarjan"));
        StdOut.println("Expected: 8");
        StdOut.println("Rank of Trie123: " + hybridTernarySearchTrie.rank("Trie123"));
        StdOut.println("Expected: 10");
        StdOut.println("Rank of Zzoom: " + hybridTernarySearchTrie.rank("Zzoom"));
        StdOut.println("Expected: 12");

        // Delete tests
        hybridTernarySearchTrie.delete("Z-Function");

        StdOut.println("\nKeys() after deleting Z-Function key");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        hybridTernarySearchTrie.delete("Rene");

        StdOut.println("\nKeys() after deleting Rene key");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }

        hybridTernarySearchTrie.delete("Re");

        StdOut.println("\nKeys() after deleting Re key");
        for (String key : hybridTernarySearchTrie.keys()) {
            StdOut.println(key);
        }
    }
}


Question: Spell checking. Write a TST client SpellChecker that takes as command-line argument the name of a file containing a dictionary of words in the English language, and then reads a string from standard input and prints out any word that is not in the dictionary. Use a string set.

Answer:
package chapter5.section2;

import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import util.Constants;
import util.FileUtil;

/**
 * Created by Rene Argento on 08/02/18.
 */
public class Exercise17_SpellChecking {

    private void spellChecker(String dictionaryFileName) {
        String filePath = Constants.FILES_PATH + dictionaryFileName;
        String[] wordsInDictionary = FileUtil.getAllStringsFromFile(filePath);

        String[] text = StdIn.readAllStrings();

        StdOut.println("Words not in the dictionary:");

        if (wordsInDictionary == null) {
            for (String word : text) {
                StdOut.println(word);
            }

            return;
        }

        StringSet stringSet = new StringSet();

        for (String word : wordsInDictionary) {
            stringSet.add(word);
        }

        for (String word : text) {
            if (!stringSet.contains(word)) {
                StdOut.println(word);
            }
        }
    }

    // Parameters example: 3.1.26_Dictionary.txt
    // This is the same dictionary file used in exercise 3.1.26
    // Standard input text: Djikstra sorting method Test good Algorithms

    // Expected output:
    // sorting
    // method
    // good

    // Dictionary file contents

    // Algorithms
    // ABC
    // Djikstra
    // Test
    // Rene
    // Binary
    // Sort
    public static void main(String[] args) {
        String fileName = args[0];
        new Exercise17_SpellChecking().spellChecker(fileName);
    }

}


Question: Typing monkeys. Suppose that a typing monkey creates random words by appending each of 26 possible letter with probability p to the current word and finishes the word with probability 1 - 26p. Write a program to estimate the frequency distribution of the lengths of words produced. If "abc" is produced more than once, count it only once.

Answer:
package chapter5.section2;

import chapter3.section4.SeparateChainingHashTable;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
import util.Constants;

/**
 * Created by Rene Argento on 17/02/18.
 */
public class Exercise22_TypingMonkeys {

    public double[] getFrequencyDistribution(int numberOfWordsToGenerate, double probability) {
        int wordsGenerated = 0;

        SeparateChainingHashTable<Integer, Integer> frequencyMap = new SeparateChainingHashTable<>();
        int maxWordLength = 0;

        StringSet wordsGeneratedStringSet = new StringSet();

        while (wordsGenerated < numberOfWordsToGenerate) {
            StringBuilder currentWord = new StringBuilder();

            while (true) {
                double currentProbability = StdRandom.uniform();

                int characterIndexToAppend = (int) (currentProbability / probability);

                // Valid characters are in the range [0, 25]
                boolean shouldFinishWord = characterIndexToAppend >= 26;

                if (shouldFinishWord) {

                    String word = currentWord.toString();
                    if (wordsGeneratedStringSet.contains(word)) {
                        // Word was already generated, so we do not count it
                        break;
                    } else {
                        wordsGeneratedStringSet.add(word);
                    }

                    wordsGenerated++;

                    int currentWordLength = currentWord.length();

                    if (currentWordLength > maxWordLength) {
                        maxWordLength = currentWordLength;
                    }

                    int frequencyCount = 0;

                    if (frequencyMap.contains(currentWordLength)) {
                        frequencyCount = frequencyMap.get(currentWordLength);
                    }

                    frequencyCount++;

                    frequencyMap.put(currentWordLength, frequencyCount);
                    break;
                }

                int nextCharacterIndex = Constants.ASC_II_UPPERCASE_LETTERS_INITIAL_INDEX + characterIndexToAppend;
                currentWord.append((char) nextCharacterIndex);
            }
        }

        double[] frequencies = new double[maxWordLength + 1];

        for (int wordLength : frequencyMap.keys()) {
            double wordLengthFrequency = frequencyMap.get(wordLength) / (double) numberOfWordsToGenerate;
            frequencies[wordLength] = wordLengthFrequency;
        }

        return frequencies;
    }

    // Parameters example: 0.025 1000
    public static void main(String[] args) {
        double probability = Double.parseDouble(args[0]);
        int numberOfWordsToGenerate = Integer.parseInt(args[1]);

        if (probability >= 0.03846) {
            throw new IllegalArgumentException("Probability must be less than 0.03846 (which is 1 / 26)");
        }

        // Release the monkey
        double[] frequencies = new Exercise22_TypingMonkeys().getFrequencyDistribution(numberOfWordsToGenerate, probability);

        StdOut.println("Frequency distribution estimate\n");
        for (int i = 0; i < frequencies.length; i++) {
            StdOut.printf("%12s %.3f\n", "Length " + i + ": ", frequencies[i]);
        }
    }
}


Question: Duplicates (revisited again). Redo Exercise 3.5.30 using StringSET (see Exercise 5.2.6) instead of HashSET. Compare the running times of the two approaches. Then use Dedup to run the experiments for N = 10^7, 10^8, and10^9, repeat the experiments for random long values and discuss the results.

Answer:
5.2.23 - Duplicates (revisited again)

Results:

            Method |   Data structure |   Values type |  Values Generated |     Max Value |  Time spent
 Array index count              Array         Integer                1000             500          0.01
 Array index count              Array         Integer                1000            1000          0.00
 Array index count              Array         Integer                1000            2000          0.00
 Array index count              Array         Integer               10000            5000          0.00
 Array index count              Array         Integer               10000           10000          0.00
 Array index count              Array         Integer               10000           20000          0.00
 Array index count              Array         Integer              100000           50000          0.02
 Array index count              Array         Integer              100000          100000          0.02
 Array index count              Array         Integer              100000          200000          0.03
 Array index count              Array         Integer             1000000          500000          0.28
 Array index count              Array         Integer             1000000         1000000          0.35
 Array index count              Array         Integer             1000000         2000000          0.37
             DeDup            HashSet         Integer                1000             500          0.00
             DeDup            HashSet         Integer                1000            1000          0.00
             DeDup            HashSet         Integer                1000            2000          0.00
             DeDup            HashSet         Integer               10000            5000          0.01
             DeDup            HashSet         Integer               10000           10000          0.01
             DeDup            HashSet         Integer               10000           20000          0.01
             DeDup            HashSet         Integer              100000           50000          0.05
             DeDup            HashSet         Integer              100000          100000          0.06
             DeDup            HashSet         Integer              100000          200000          0.06
             DeDup            HashSet         Integer             1000000          500000          1.30
             DeDup            HashSet         Integer             1000000         1000000          1.34
             DeDup            HashSet         Integer             1000000         2000000          2.18
             DeDup          StringSet         Integer                1000             500          0.01
             DeDup          StringSet         Integer                1000            1000          0.00
             DeDup          StringSet         Integer                1000            2000          0.00
             DeDup          StringSet         Integer               10000            5000          0.02
             DeDup          StringSet         Integer               10000           10000          0.02
             DeDup          StringSet         Integer               10000           20000          0.03
             DeDup          StringSet         Integer              100000           50000          0.29
             DeDup          StringSet         Integer              100000          100000          0.35
             DeDup          StringSet         Integer              100000          200000          0.46
             DeDup          StringSet         Integer             1000000          500000          6.23
             DeDup          StringSet         Integer             1000000         1000000          7.20
             DeDup          StringSet         Integer             1000000         2000000          8.13
 Array index count              Array            Long                1000             500          0.00
 Array index count              Array            Long                1000            1000          0.00
 Array index count              Array            Long                1000            2000          0.00
 Array index count              Array            Long               10000            5000          0.00
 Array index count              Array            Long               10000           10000          0.00
 Array index count              Array            Long               10000           20000          0.00
 Array index count              Array            Long              100000           50000          0.02
 Array index count              Array            Long              100000          100000          0.02
 Array index count              Array            Long              100000          200000          0.02
 Array index count              Array            Long             1000000          500000          0.24
 Array index count              Array            Long             1000000         1000000          0.38
 Array index count              Array            Long             1000000         2000000          0.39
             DeDup            HashSet            Long                1000             500          0.00
             DeDup            HashSet            Long                1000            1000          0.00
             DeDup            HashSet            Long                1000            2000          0.00
             DeDup            HashSet            Long               10000            5000          0.01
             DeDup            HashSet            Long               10000           10000          0.01
             DeDup            HashSet            Long               10000           20000          0.01
             DeDup            HashSet            Long              100000           50000          0.06
             DeDup            HashSet            Long              100000          100000          0.08
             DeDup            HashSet            Long              100000          200000          0.09
             DeDup            HashSet            Long             1000000          500000          1.51
             DeDup            HashSet            Long             1000000         1000000          1.57
             DeDup            HashSet            Long             1000000         2000000          2.20
             DeDup          StringSet            Long                1000             500          0.02
             DeDup          StringSet            Long                1000            1000          0.01
             DeDup          StringSet            Long                1000            2000          0.00
             DeDup          StringSet            Long               10000            5000          0.03
             DeDup          StringSet            Long               10000           10000          0.03
             DeDup          StringSet            Long               10000           20000          0.03
             DeDup          StringSet            Long              100000           50000          0.30
             DeDup          StringSet            Long              100000          100000          0.40
             DeDup          StringSet            Long              100000          200000          0.43
             DeDup          StringSet            Long             1000000          500000          6.40
             DeDup          StringSet            Long             1000000         1000000          7.29
             DeDup          StringSet            Long             1000000         2000000          8.19

Results for DeDup with 10^7 and 10^8 values generated (10^9 configuration was not used due to operational system limitations)

            Method |   Data structure |   Values type |  Values Generated |     Max Value |  Time spent
             DeDup            HashSet         Integer            10000000         5000000         19.15
             DeDup            HashSet         Integer            10000000        10000000         24.70
             DeDup            HashSet         Integer            10000000        20000000         30.05
             DeDup            HashSet         Integer           100000000        50000000        513.71
             DeDup            HashSet         Integer           100000000       100000000        823.40
             DeDup            HashSet         Integer           100000000       200000000       2362.58
             DeDup          StringSet         Integer            10000000         5000000        106.45
             DeDup          StringSet         Integer            10000000        10000000        115.64
             DeDup          StringSet         Integer            10000000        20000000        128.20
             DeDup          StringSet         Integer           100000000        50000000       2131.47
             DeDup          StringSet         Integer           100000000       100000000       5311.38
             DeDup          StringSet         Integer           100000000       200000000      11004.70
             DeDup            HashSet            Long            10000000         5000000        126.06
             DeDup            HashSet            Long            10000000        10000000         39.92
             DeDup            HashSet            Long            10000000        20000000         29.64
             DeDup            HashSet            Long           100000000        50000000        530.19
             DeDup            HashSet            Long           100000000       100000000        890.28
             DeDup            HashSet            Long           100000000       200000000       1556.39
             DeDup          StringSet            Long            10000000         5000000        151.01
             DeDup          StringSet            Long            10000000        10000000        122.93
             DeDup          StringSet            Long            10000000        20000000        141.92
             DeDup          StringSet            Long           100000000        50000000       2610.32
             DeDup          StringSet            Long           100000000       100000000       3423.40
             DeDup          StringSet            Long           100000000       200000000       9592.43

Dedup using a string set takes longer (more than 4 times more) to compute the distinct values than dedup using a hash set. The main reason for this seems to be because string sets only allow strings to be stored and every int and long key must be cast to a String before being inserted into the data structure. Casting is a costly operation, especially when it is done thousands or millions of times.
As seen on exercise 3.5.30, dedup using a hash set takes longer to compute the distinct values than the method counting frequencies in the array for both random integer and random long values. This is expected because even though hash sets and arrays have constant search time, the hash set requires autoboxing and unboxing for the int and long keys, which adds time in the operations.
Also, dedup does up to two operations per value generated: one to check if the set contains the value and (if it does not contain the value) another operation to insert it in the set whereas the array simply increases the value's index count. The dedup implementation in the book could be improved since the extra check to see if the set contains the value is not necessary. Sets do not allow duplicates, so it would internally ignore any attempts to insert a duplicate value.
As expected, the experiments with dedup for N = 10^7 and 10^8 using a string set are also slower to compute the distinct values than with dedup using a hash set.

Correction: the experiment table above is incomplete for the prompt as written because it reports `Dedup` only through `N = 10^8`; the requested `N = 10^9` case is not present. A complete dataset answer should either add the `10^9` timing for both random `int` and random `long` values or explicitly state that the run was skipped because of time/memory limits. The qualitative comparison remains that trie-based `StringSET` dedup is slower than hash-based `HashSET` for this workload, while direct array counting is fastest when the key universe is small enough to index directly.


Question: Spell checker. Redo Exercise 3.5.31, which uses the file dictionary.txt from the booksite and the BlackFilter client on page 491 to print all misspelled words in a text file. Compare the performance of TrieST and TST for the file war.txt with this client and discuss the results.

Answer:
// Exercise24_SpellChecker.java
package chapter5.section2;

import chapter3.section5.HashSet;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.Stopwatch;
import util.Constants;
import util.FileUtil;

/**
 * Created by Rene Argento on 18/02/18.
 */
public class Exercise24_SpellChecker {

    private class BlackFilter {

        public HashSet<String> filterUsingTrie(String dictionaryFilePath, String warAndPeaceFilePath) {
            HashSet<String> filteredWords = new HashSet<>();

            // The value field is not used, but it is required by the trie
            Trie<Boolean> trie = new Trie<>();

            In in = new In(dictionaryFilePath);
            while (!in.isEmpty()) {
                trie.put(in.readString(), true);
            }

            String[] allWords = FileUtil.getAllStringsFromFile(warAndPeaceFilePath);

            if (allWords == null) {
                return filteredWords;
            }

            for (String word : allWords) {
                if (!trie.contains(word)) {
                    filteredWords.add(word);
                }
            }

            return filteredWords;
        }

        public HashSet<String> filterUsingTST(String dictionaryFilePath, String warAndPeaceFilePath) {
            HashSet<String> filteredWords = new HashSet<>();

            // The value field is not used, but it is required by the ternary search trie
            TernarySearchTrie<Boolean> ternarySearchTrie = new TernarySearchTrie<>();

            In in = new In(dictionaryFilePath);
            while (!in.isEmpty()) {
                ternarySearchTrie.put(in.readString(), true);
            }

            String[] allWords = FileUtil.getAllStringsFromFile(warAndPeaceFilePath);

            if (allWords == null) {
                return filteredWords;
            }

            for (String word : allWords) {
                if (!ternarySearchTrie.contains(word)) {
                    filteredWords.add(word);
                }
            }

            return filteredWords;
        }
    }

    // There was no dictionary.txt file on the booksite, so I suspect it has been renamed to commonwords.txt
    // Parameter example: common_words.txt

    private void doExperiment(String[] args) {
        String dictionaryFileName = args[0];
        String dictionaryFilePath = Constants.FILES_PATH + dictionaryFileName;

        String warAndPeaceFilePath = Constants.FILES_PATH + Constants.WAR_AND_PEACE_FILE;

        BlackFilter blackFilter = new BlackFilter();

        Stopwatch stopwatch = new Stopwatch();
        blackFilter.filterUsingTrie(dictionaryFilePath, warAndPeaceFilePath);
        double timeSpentWithTrie = stopwatch.elapsedTime();

        stopwatch = new Stopwatch();
        blackFilter.filterUsingTST(dictionaryFilePath, warAndPeaceFilePath);
        double timeSpentWithTST = stopwatch.elapsedTime();

        StdOut.printf("%19s %14s\n", "Time spent trie | ", "Time spent TST");
        printResults(timeSpentWithTrie, timeSpentWithTST);
    }

    private void printResults(double trieTime, double ternarySearchTrieTime) {
        StdOut.printf("%16.2f %17.2f\n", trieTime, ternarySearchTrieTime);
    }

    public static void main(String[] args) {
        new Exercise24_SpellChecker().doExperiment(args);
    }
}

Additional notes/results:
5.2.24 - Spell checker

Results:

 Time spent trie |  Time spent TST
            0.71              0.44

The ternary search trie had the best result, computing misspelled words in almost half the time required by the trie (the TST took 0.44 seconds against the 0.71 seconds taken by the trie).
This is an unexpected result because tries make a constant number of character compares during the search for words, while ternary search tries make a logarithmic number of character compares during these same searches.
One possible explanation for this result is that whenever a new node is created in the trie, an array of size 256 is also created. The cumulative time needed for the creation of these arrays may take a performance hit when dealing with many operations.


Question: Give the dfa[][] array for the Knuth-Morris-Pratt algorithm for the pattern A A A A A A A A A, and draw the DFA, in the style of the figures in the text.

Answer:
5.3.2

pattern: A A A A A A A A A

           j   0
pat.charAt(j)  A
           A   1
  dfa[][j] B   0
           C   0
           …   0

    j    A
    0    —>   1
  /  ^
 /    \
 -B,C,…-

               X
           j   0   1
pat.charAt(j)  A   A
           A   1   2
  dfa[][j] B   0   0
           C   0   0
           …   0   0

         A     j   A
    0    —>    1   —>   2
  /  ^ <-       \
 /    \   \      \
 -B,C,…-   --B,C,…-

                   X
           j   0   1   2
pat.charAt(j)  A   A   A
           A   1   2   3
  dfa[][j] B   0   0   0
           C   0   0   0
           …   0   0   0

         A         A     j    A
    0    —>    1   —>    2    —>   3
  /  ^ <—-      \          \
 /    \    \     \          \
 -B,C,…-    -B,C,…- <-—B,C,…-

                       X
           j   0   1   2   3
pat.charAt(j)  A   A   A   A
           A   1   2   3   4
  dfa[][j] B   0   0   0   0
           C   0   0   0   0
           …   0   0   0   0

         A         A           A    j    A
    0    —>    1   —>    2     —>   3    —>   4
  /  ^ <—-      \          \         \
 /    \    \     \          \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…-

                           X
           j   0   1   2   3   4
pat.charAt(j)  A   A   A   A   A
           A   1   2   3   4   5
  dfa[][j] B   0   0   0   0   0
           C   0   0   0   0   0
           …   0   0   0   0   0

         A         A          A          A    j    A
    0    —>    1   —>    2    —>    3    —>   4    —>   5
  /  ^ <—-      \          \         \         \
 /    \    \     \          \         \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…-

                               X
           j   0   1   2   3   4   5
pat.charAt(j)  A   A   A   A   A   A
           A   1   2   3   4   5   6
  dfa[][j] B   0   0   0   0   0   0
           C   0   0   0   0   0   0
           …   0   0   0   0   0   0

         A         A          A          A         A   j     A
    0    —>    1   —>    2    —>    3    —>   4    —>  5     —>   6
  /  ^ <—-      \          \         \         \         \
 /    \    \     \          \         \         \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…-

                                   X
           j   0   1   2   3   4   5   6
pat.charAt(j)  A   A   A   A   A   A   A
           A   1   2   3   4   5   6   7
  dfa[][j] B   0   0   0   0   0   0   0
           C   0   0   0   0   0   0   0
           …   0   0   0   0   0   0   0

         A         A          A          A         A        A     j    A
    0    —>    1   —>    2    —>    3    —>   4    —>  5    —>    6    —>   7
  /  ^ <—-      \          \         \         \         \         \
 /    \    \     \          \         \         \         \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…-

                                       X
           j   0   1   2   3   4   5   6   7
pat.charAt(j)  A   A   A   A   A   A   A   A
           A   1   2   3   4   5   6   7   8
  dfa[][j] B   0   0   0   0   0   0   0   0
           C   0   0   0   0   0   0   0   0
           …   0   0   0   0   0   0   0   0

         A         A          A          A         A        A          A    j    A
    0    —>    1   —>    2    —>    3    —>   4    —>  5    —>    6    —>   7    —>   8
  /  ^ <—-      \          \         \         \         \         \         \
 /    \    \     \          \         \         \         \         \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…-

                                           X
           j   0   1   2   3   4   5   6   7   8
pat.charAt(j)  A   A   A   A   A   A   A   A   A
           A   1   2   3   4   5   6   7   8   9
  dfa[][j] B   0   0   0   0   0   0   0   0   0
           C   0   0   0   0   0   0   0   0   0
           …   0   0   0   0   0   0   0   0   0

         A         A          A          A         A        A          A         A    j    A
    0    —>    1   —>    2    —>    3    —>   4    —>  5    —>    6    —>   7    —>   8    —>   9
  /  ^ <—-      \          \         \         \         \         \         \         \
 /    \    \     \          \         \         \         \         \         \         \
 -B,C,…-    -B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…- <-—B,C,…-


Question: Give the dfa[][] array for the Knuth-Morris-Pratt algorithm for the pattern A B R A C A D A B R A, and draw the DFA, in the style of the figures in the text.

Answer:
5.3.3

pattern: A B R A C A D A B R A

           j   0
pat.charAt(j)  A
           A   1
  dfa[][j] B   0
           C   0
           D   0
           …   0
           R   0
           …   0

        j          A
        0          —>   1
  /         ^
 /           \
 -B,C,D,…,R,…-

               X
           j   0   1
pat.charAt(j)  A   B
           A   1   1
  dfa[][j] B   0   2
           C   0   0
           D   0   0
           …   0   0
           R   0   0
           …   0   0

                   A     j      B
        0          —>    1      —>   2
  /         ^           ^ \
 /           \         /   \
 -B,C,D,…,R,…-         - A -
               \            \
                 -C,D,…,R,…-

               X
           j   0   1   2
pat.charAt(j)  A   B   R
           A   1   1   1
  dfa[][j] B   0   2   0
           C   0   0   0
           D   0   0   0
           …   0   0   0
           R   0   0   3
           …   0   0   0

                   A              B         j    R
        0          —>    1        —>        2    —>   3
  /         ^           ^ \                 |
 /           \         /   \                |
 -B,C,D,…,R,…-         - A -  <————-—-A-—-—-
               \            \               |
                 —C,D,…,R,…- <—B,C,D,…,Q,S,…—

               X
           j   0   1   2   3
pat.charAt(j)  A   B   R   A
           A   1   1   1   4
  dfa[][j] B   0   2   0   0
           C   0   0   0   0
           D   0   0   0   0
           …   0   0   0   0
           R   0   0   3   0
           …   0   0   0   0

                   A                B               R       j    A
        0          —>    1          —>       2      —>      3    —>   4
  /         ^           ^ \                  |              |
 /           \         /   \                 |              |
 -B,C,D,…,R,…-         - A -  <————-—-A-—-——-               |
               \            \                |              |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…—

                   X
           j   0   1   2   3   4
pat.charAt(j)  A   B   R   A   C
           A   1   1   1   4   1
  dfa[][j] B   0   2   0   0   2
           C   0   0   0   0   5
           D   0   0   0   0   0
           …   0   0   0   0   0
           R   0   0   3   0   0
           …   0   0   0   0   0

                                             ————————-——-B————————-——--—
                   A              B          V      R            A    j|    C
        0          —>    1        —>         2      —>      3    —>    4    —>   5
  /         ^           ^ \                  |              |          |
 /           \         /   \                 |              |          |
 -B,C,D,…,R,…-         - A -  <————-—-A-—-——-  <—————-————--|-————A-——-
               \            \                |              |          |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…—

               X
           j   0   1   2   3   4   5
pat.charAt(j)  A   B   R   A   C   A
           A   1   1   1   4   1   6
  dfa[][j] B   0   2   0   0   2   0
           C   0   0   0   0   5   0
           D   0   0   0   0   0   0
           …   0   0   0   0   0   0
           R   0   0   3   0   0   0
           …   0   0   0   0   0   0

                                             ————————-——-B————————-——-—
                   A               B         V      R           A     |      C        j     A
        0          —>    1         —>        2      —>     3    —>    4      —>       5     —>   6
  /         ^           ^ \                  |             |          |               |
 /           \         /   \                 |             |          |               |
 -B,C,D,…,R,…-         - A -  <————-—-A-————- <-—————-—————|-————A-——-                |
               \            \                |             |          |               |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…-

                   X
           j   0   1   2   3   4   5   6
pat.charAt(j)  A   B   R   A   C   A   D
           A   1   1   1   4   1   6   1
  dfa[][j] B   0   2   0   0   2   0   2
           C   0   0   0   0   5   0   0
           D   0   0   0   0   0   0   7
           …   0   0   0   0   0   0   0
           R   0   0   3   0   0   0   0
           …   0   0   0   0   0   0   0

                                             ————————-——-B————————-——— <————————-——--B————————-—————
                   A               B         V      R            A     |      C             A     j|     D
        0          —>    1         —>        2      —>      3    —>    4      —>      5     —>     6     —>   7
  /         ^           ^ \                  |              |          |              |            |
 /           \         /   \                 |              |          |              |            |
 -B,C,D,…,R,…-         - A -  <————-—-A-————- <-—————-—————-|-————A-——-  <—————-——————|-————A-——-—-
               \            \                |              |          |              |            |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…- <—C,E,…,R,…-

               X
           j   0   1   2   3   4   5   6   7
pat.charAt(j)  A   B   R   A   C   A   D   A
           A   1   1   1   4   1   6   1   8
  dfa[][j] B   0   2   0   0   2   0   2   0
           C   0   0   0   0   5   0   0   0
           D   0   0   0   0   0   0   7   0
           …   0   0   0   0   0   0   0   0
           R   0   0   3   0   0   0   0   0
           …   0   0   0   0   0   0   0   0

                                             ————————-——-B————————-——-—— <————————-——-B————————-————
                   A               B         V      R            A      |      C           A       |       D      j     A
        0          —>    1         —>        2      —>      3    —>     4      —>     5    —>      6       —>     7     —>   8
  /         ^           ^ \                  |              |           |             |            |              |
 /           \         /   \                 |              |           |             |            |              |
 -B,C,D,…,R,…-         - A -  <————-—-A-————-  <—————-—————-|-————A-———- <—————-—————-|-————A-——-—-               |
               \            \                |              |           |             |            |              |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…- <—C,E,…,R,…- <—B,C,D,…,R,…—

                   X
           j   0   1   2   3   4   5   6   7   8
pat.charAt(j)  A   B   R   A   C   A   D   A   B
           A   1   1   1   4   1   6   1   8   1
  dfa[][j] B   0   2   0   0   2   0   2   0   9
           C   0   0   0   0   5   0   0   0   0
           D   0   0   0   0   0   0   7   0   0
           …   0   0   0   0   0   0   0   0   0
           R   0   0   3   0   0   0   0   0   0
           …   0   0   0   0   0   0   0   0   0

                                             ————————-——-B————————-——-- <————————-——-B————————-—————
                   A              B          V      R            A     |      C            A       |       D             A     j     B
        0          —>    1        —>         2      —>      3    —>    4      —>      5    —>      6       —>     7      —>    8     —>   9
  /         ^           ^ \                  |              |          |              |            |              |            |
 /           \         /   \                 |              |          |              |            |              |            |
 -B,C,D,…,R,…-         - A -  <————-—-A-————-  <—————-————--|-————A-——-  <—————-——————|-————A-————— <——————-—————-|-—-————A-——-
               \            \                |              |          |              |            |              |            |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…- <—C,E,…,R,…- <—B,C,D,…,R,…— <—C,D,…,R,…—

                       X
           j   0   1   2   3   4   5   6   7   8    9
pat.charAt(j)  A   B   R   A   C   A   D   A   B    R
           A   1   1   1   4   1   6   1   8   1    1
  dfa[][j] B   0   2   0   0   2   0   2   0   9    0
           C   0   0   0   0   5   0   0   0   0    0
           D   0   0   0   0   0   0   7   0   0    0
           …   0   0   0   0   0   0   0   0   0    0
           R   0   0   3   0   0   0   0   0   0   10
           …   0   0   0   0   0   0   0   0   0    0

                                             ————————-——-B————————-———— <————————-——-B————————-—————
                   A             B           V      R            A     |      C            A       |       D             A             B        j     R
        0          —>    1       —>          2      —>      3    —>    4      —>      5    —>      6       —>     7      —>    8       —>       9     —>   10
  /         ^           ^ \                  |              |          |              |            |              |            |                |
 /           \         /   \                 |              |          |              |            |              |            |                |
 -B,C,D,…,R,…-         - A -  <————-—-A-————-  <—————-——————|-————A-——-  <—————-————-—|-————A-——-—— <--—————-————-|-—-————A-——- <—-————A-————---
               \            \                |              |          |              |            |              |            |                |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…- <—C,E,…,R,…- <—B,C,D,…,R,…— <—C,D,…,R,…— <—B,C,D,…,Q,S,…—

                           X
           j   0   1   2   3   4   5   6   7   8    9    10
pat.charAt(j)  A   B   R   A   C   A   D   A   B    R     A
           A   1   1   1   4   1   6   1   8   1    1    11
  dfa[][j] B   0   2   0   0   2   0   2   0   9    0     0
           C   0   0   0   0   5   0   0   0   0    0     0
           D   0   0   0   0   0   0   7   0   0    0     0
           …   0   0   0   0   0   0   0   0   0    0     0
           R   0   0   3   0   0   0   0   0   0   10     0
           …   0   0   0   0   0   0   0   0   0    0     0

                                             ————————-——-B————————-———— <————————-——-B————————-—————
                   A             B           V      R            A     |      C            A       |       D             A             B               R       j   A
        0          —>    1       —>          2      —>      3    —>    4      —>      5    —>      6       —>     7      —>    8       —>       9      —>     10   —>   11
  /         ^           ^ \                  |              |          |              |            |              |            |                |              |
 /           \         /   \                 |              |          |              |            |              |            |                |              |
 -B,C,D,…,R,…-         - A -  <————-—-A-————-  <—————-——————|-————A-——-  <—————-————-—|-————A-——-—— <--—————-————-|-—-————A-——- <—-————A-————---               |
               \            \                |              |          |              |            |              |            |                |              |
                 --C,D,…,R,…- <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…— <-D,…,R,…— <—B,C,D,…,R,…- <—C,E,…,R,…- <—B,C,D,…,R,…— <—C,D,…,R,…— <—B,C,D,…,Q,S,…— <—B,C,D,…,R,…—


Question: Write an efficient method that takes a string txt and an integer M as arguments and returns the position of the first occurrence of M consecutive blanks in the string, txt.length if there is no such occurrence. Estimate the number of character compares used by your method, on typical text and in the worst case.

Answer:
package chapter5.section3;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 24/02/18.
 */
// Thanks to AdamShamaa (https://github.com/AdamShamaa) for suggesting an optimization to this exercise.
// https://github.com/reneargento/algorithms-sedgewick-wayne/issues/174

// Number of character compares:
    // Typical case: N / 2
    // Worst case: N
public class Exercise4 {

    public int findBlankCharacters(String text, int mSpaces) {
        int textLength = text.length();
        int consecutiveBlanks;
        int i;

        for (i = 0, consecutiveBlanks = 0; i < textLength && consecutiveBlanks < mSpaces; i++) {
            if (text.charAt(i) == ' ') {
                consecutiveBlanks++;
            } else {
                consecutiveBlanks = 0;
            }
        }

        if (consecutiveBlanks == mSpaces) {
            return i - mSpaces;	  //found
        } else {
            return textLength;    //not found
        }
    }

    public static void main(String[] args) {
        Exercise4 exercise4 = new Exercise4();

        String text = " abacada  abr   braca  brabrabracad";

        int index1 = exercise4.findBlankCharacters(text, 1);
        StdOut.println("Index 1: " + index1 + " Expected: 0");

        int index2 = exercise4.findBlankCharacters(text, 2);
        StdOut.println("Index 2: " + index2 + " Expected: 8");

        int index3 = exercise4.findBlankCharacters(text, 3);
        StdOut.println("Index 3: " + index3 + " Expected: 13");

        int index4 = exercise4.findBlankCharacters(text, 4);
        StdOut.println("Index 4: " + index4 + " Expected: 35");
    }

}


Question: Develop a brute-force substring search implementation BruteForceRL that processes the pattern from right to left (a simplified version of ALGORITHM 5.7).

Answer:
package chapter5.section3;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 25/02/18.
 */
public class Exercise15 {

    public class BruteForceRL {

        private String pattern;
        private int patternLength;

        BruteForceRL(String pattern) {
            this.pattern = pattern;
            patternLength = pattern.length();
        }

        public int search(String text) {

            int textLength = text.length();

            for (int textIndex = 0; textIndex <= textLength - patternLength; textIndex++) {

                int patternIndex;
                for (patternIndex = patternLength - 1; patternIndex >= 0; patternIndex--) {
                    if (text.charAt(textIndex + patternIndex) != pattern.charAt(patternIndex)) {
                        break;
                    }
                }

                if (patternIndex == -1) {
                    return textIndex;  // found
                }
            }

            return textLength;         // not found
        }

    }

    public static void main(String[] args) {
        Exercise15 exercise15 = new Exercise15();

        String text = "abacadabrabracabracadabrabrabracad";

        String pattern1 = "abracadabra";
        BruteForceRL bruteForceRL1 = exercise15.new BruteForceRL(pattern1);
        int index1 = bruteForceRL1.search(text);
        StdOut.println("Index 1: " + index1 + " Expected: 14");

        String pattern2 = "rab";
        BruteForceRL bruteForceRL2 = exercise15.new BruteForceRL(pattern2);
        int index2 = bruteForceRL2.search(text);
        StdOut.println("Index 2: " + index2 + " Expected: 8");

        String pattern3 = "bcara";
        BruteForceRL bruteForceRL3 = exercise15.new BruteForceRL(pattern3);
        int index3 = bruteForceRL3.search(text);
        StdOut.println("Index 3: " + index3 + " Expected: 34");

        String pattern4 = "rabrabracad";
        BruteForceRL bruteForceRL4 = exercise15.new BruteForceRL(pattern4);
        int index4 = bruteForceRL4.search(text);
        StdOut.println("Index 4: " + index4 + " Expected: 23");

        String pattern5 = "abacad";
        BruteForceRL bruteForceRL5 = exercise15.new BruteForceRL(pattern5);
        int index5 = bruteForceRL5.search(text);
        StdOut.println("Index 5: " + index5 + " Expected: 0");
    }

}


Question: Give the right[] array computed by the constructor in ALGORITHM 5.7 for the pattern A B R A C A D A B R A.

Answer:
5.3.6

pattern: A B R A C A D A B R A

    A  B  R  A  C  A  D  A  B  R  A
c   0  1  2  3  4  5  6  7  8  9 10  right[c]
A   0  0  0  3  3  5  5  7  7  7 10    10
B  -1  1  1  1  1  1  1  1  8  8  8     8
C  -1 -1 -1 -1  4  4  4  4  4  4  4     4
D  -1 -1 -1 -1 -1 -1  6  6  6  6  6     6
…                                      -1
R  -1 -1  2  2  2  2  2  2  2  9  9     9
…                                      -1


Question: Add to KMP a count() method to count occurrences and a searchAll() method to print all occurrences.

Answer:
package chapter5.section3;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 25/02/18.
 */
public class Exercise8 {

    public class KnuthMorrisPrattSearchAll extends KnuthMorrisPratt {

        KnuthMorrisPrattSearchAll(String pattern) {
            super(pattern);
        }

        // Count the occurrences of pattern in the text
        public int count(String text) {
            int count = 0;

            int occurrenceIndex = searchFromIndex(text, 0);

            while (occurrenceIndex != text.length()) {
                count++;
                occurrenceIndex = searchFromIndex(text, occurrenceIndex + 1);
            }

            return count;
        }

        // Prints all the occurrences of pattern in the text
        public void searchAll(String text) {
            int occurrenceIndex = searchFromIndex(text, 0);

            if (occurrenceIndex == text.length()) {
                StdOut.println("No occurrences");
                return;
            }

            while (occurrenceIndex != text.length()) {
                StdOut.println("Pattern found at index " + occurrenceIndex);
                occurrenceIndex = searchFromIndex(text, occurrenceIndex + 1);
            }
        }

        // Searches for the pattern in the text starting at specified index
        protected int searchFromIndex(String text, int textStartIndex) {
            int textIndex;
            int patternIndex;
            int textLength = text.length();
            int patternLength = pattern.length();

            for (textIndex = textStartIndex, patternIndex = 0; textIndex < textLength && patternIndex < patternLength;
                 textIndex++) {
                patternIndex = dfa[text.charAt(textIndex)][patternIndex];
            }
            if (patternIndex == patternLength) {
                return textIndex - patternLength; // found
            } else {
                return textLength;                // not found
            }
        }
    }

    public static void main(String[] args) {
        Exercise8 exercise8 = new Exercise8();

        String text = "abcdrenetestreneabdreneabcdd";

        String pattern1 = "rene";
        KnuthMorrisPrattSearchAll knuthMorrisPrattSearchAll1 = exercise8.new KnuthMorrisPrattSearchAll(pattern1);
        int count1 = knuthMorrisPrattSearchAll1.count(text);
        StdOut.println("Count 1: " + count1 + " Expected: 3");

        StdOut.println("Occurrences");
        knuthMorrisPrattSearchAll1.searchAll(text);
        StdOut.println("Expected: 4, 12, 19\n");

        String pattern2 = "abcd";
        KnuthMorrisPrattSearchAll knuthMorrisPrattSearchAll2 = exercise8.new KnuthMorrisPrattSearchAll(pattern2);
        int count2 = knuthMorrisPrattSearchAll2.count(text);
        StdOut.println("Count 2: " + count2 + " Expected: 2");

        StdOut.println("Occurrences");
        knuthMorrisPrattSearchAll2.searchAll(text);
        StdOut.println("Expected: 0, 23\n");

        String pattern3 = "d";
        KnuthMorrisPrattSearchAll knuthMorrisPrattSearchAll3 = exercise8.new KnuthMorrisPrattSearchAll(pattern3);
        int count3 = knuthMorrisPrattSearchAll3.count(text);
        StdOut.println("Count 3: " + count3 + " Expected: 4");

        StdOut.println("Occurrences");
        knuthMorrisPrattSearchAll3.searchAll(text);
        StdOut.println("Expected: 3, 18, 26, 27\n");

        String pattern4 = "zzz";
        KnuthMorrisPrattSearchAll knuthMorrisPrattSearchAll4 = exercise8.new KnuthMorrisPrattSearchAll(pattern4);
        int count4 = knuthMorrisPrattSearchAll4.count(text);
        StdOut.println("Count 4: " + count4 + " Expected: 0");

        StdOut.println("Occurrences");
        knuthMorrisPrattSearchAll4.searchAll(text);
        StdOut.println("Expected: No occurrences");
    }

}


Question: Add to RabinKarp a count() method to count occurrences and a searchAll() method to print all occurrences.

Answer:
package chapter5.section3;

import edu.princeton.cs.algs4.StdOut;

/**
 * Created by Rene Argento on 25/02/18.
 */
public class Exercise10 {

    public class RabinKarpSearchAll extends RabinKarp {

        RabinKarpSearchAll(String pattern, boolean isMonteCarloVersion) {
            super(pattern, isMonteCarloVersion);
        }

        // Count the occurrences of pattern in the text
        public int count(String text) {
            int count = 0;

            int occurrenceIndex = searchFromIndex(text, 0);

            while (occurrenceIndex != text.length()) {
                count++;

                if (occurrenceIndex + 1 >= text.length()) {
                    break;
                }

                occurrenceIndex = searchFromIndex(text, occurrenceIndex + 1);
            }

            return count;
        }

        // Prints all the occurrences of pattern in the text
        public void searchAll(String text) {
            int occurrenceIndex = searchFromIndex(text, 0);

            if (occurrenceIndex == text.length()) {
                StdOut.println("No occurrences");
                return;
            }

            while (occurrenceIndex != text.length()) {
                StdOut.println("Pattern found at index " + occurrenceIndex);

                if (occurrenceIndex + 1 >= text.length()) {
                    break;
                }

                occurrenceIndex = searchFromIndex(text, occurrenceIndex + 1);
            }
        }

        // Searches for the pattern in the text starting at specified index
        protected int searchFromIndex(String text, int textStartIndex) {
            String eligibleText = text.substring(textStartIndex);

            int textLength = eligibleText.length();

            if (textLength < patternLength) {
                return textStartIndex + textLength;  // no match
            }

            long textHash = hash(eligibleText);

            if (patternHash == textHash && check(eligibleText, 0)) {
                return textStartIndex;  // match
            }

            for (int textIndex = patternLength; textIndex < textLength; textIndex++) {
                // Remove leading character, add trailing character, check for match
                textHash = (textHash + largePrimeNumber - rm * eligibleText.charAt(textIndex - patternLength) % largePrimeNumber)
                        % largePrimeNumber;
                textHash = (textHash * alphabetSize + eligibleText.charAt(textIndex)) % largePrimeNumber;

                int offset = textIndex - patternLength + 1;

                if (patternHash == textHash && check(eligibleText, offset)) {
                    return textStartIndex + offset;  // match
                }
            }

            return textStartIndex + textLength;      // no match
        }

    }

    public static void main(String[] args) {
        Exercise10 exercise10 = new Exercise10();

        String text = "abcdrenetestreneabdreneabcdd";

        String pattern1 = "rene";
        RabinKarpSearchAll rabinKarpSearchAll1 = exercise10.new RabinKarpSearchAll(pattern1, true);
        int count1 = rabinKarpSearchAll1.count(text);
        StdOut.println("Count 1: " + count1 + " Expected: 3");

        StdOut.println("Occurrences");
        rabinKarpSearchAll1.searchAll(text);
        StdOut.println("Expected: 4, 12, 19\n");

        String pattern2 = "abcd";
        RabinKarpSearchAll rabinKarpSearchAll2 = exercise10.new RabinKarpSearchAll(pattern2, true);
        int count2 = rabinKarpSearchAll2.count(text);
        StdOut.println("Count 2: " + count2 + " Expected: 2");

        StdOut.println("Occurrences");
        rabinKarpSearchAll2.searchAll(text);
        StdOut.println("Expected: 0, 23\n");

        String pattern3 = "d";
        RabinKarpSearchAll rabinKarpSearchAll3 = exercise10.new RabinKarpSearchAll(pattern3, true);
        int count3 = rabinKarpSearchAll3.count(text);
        StdOut.println("Count 3: " + count3 + " Expected: 4");

        StdOut.println("Occurrences");
        rabinKarpSearchAll3.searchAll(text);
        StdOut.println("Expected: 3, 18, 26, 27\n");

        String pattern4 = "zzz";
        RabinKarpSearchAll rabinKarpSearchAll4 = exercise10.new RabinKarpSearchAll(pattern4, true);
        int count4 = rabinKarpSearchAll4.count(text);
        StdOut.println("Count 4: " + count4 + " Expected: 0");

        StdOut.println("Occurrences");
        rabinKarpSearchAll4.searchAll(text);
        StdOut.println("Expected: No occurrences");
    }

}