简体   繁体   中英

Changing object reference in Java

I am trying to implement a suffix trie in Java. A trie has a root node and connected to it are edges. However, when implementing functions such as constructTrie(T) (constructs a trie given a String T) or substring(S,T) (checks whether S is a substring of T), I am keeping a current node cNode which changes throughout the code depending on which node I am considering.

I am not sure if I'm changing cNode 's value correctly. The following is class Trie .

import java.util.*;

class Trie{

    protected Node root = null;

    public Trie(){
        Node n = new Node();
        root = n;
    }

    // Constructs a trie for a given string T
    public void constructTrie(String T){
        ArrayList<String> suffixArray = new ArrayList<String>();
        T += "#"; // Terminator
        int length = T.length();
        // Creates suffix array and removes first letter with every iteration
        for(int i=0; i<length; i++){
            suffixArray.add(T);
            T = T.substring(1); 
        }

        // Goes through suffix array
        for(int i=0; i<length; i++){  
            Node cNode = null; 
            cNode = root; // Current node
            int j = 0;
            // Goes through each letter of an entry in the suffix array
            while(j < (suffixArray.get(i)).length()){ 
                int index = cNode.findEdge((suffixArray.get(i)).charAt(j));
                // If an edge is found at the root with the current letter, update cNode and remove the letter from word
                if(index != -1){
                    cNode = cNode.getEdge(index).getNode(); // Gets node pointed at by edge and sets it as current node
                    String replace = (suffixArray.get(i)).substring(1);
                    suffixArray.set(0, replace); // Erases first letter of suffix
                    j++;
                    System.out.println(i + " " + j +  " " + replace);
                }
                // If an edge is not found at the root, write the whole word
                else{ 
                    for(int k=0; k<(suffixArray.get(i)).length(); k++){
                        Edge e = new Edge((suffixArray.get(i)).charAt(k)); // Creates edge with current letter of current entry of the suffix array
                        Node n = new Node(); // Creates node to be pointed at by edge
                        e.setNode(n);
                        cNode.newEdge(e);
                        cNode = n; // Updates current node
                    }
                    j = (suffixArray.get(i)).length(); // If the word is written, we break from the while and move on to the next suffix array entry
                }
            }
        }
    }

    // Checks if S is a substring of T
    public boolean substring(String S, String T){
        constructTrie(T);
        Node cNode = null;
        cNode = root;

        int index;
        for(int i=0; i<S.length(); i++){
            index = cNode.findEdge(S.charAt(i));
            if(index == -1)
                return false; // Substring was not found because a path was not followed
            cNode = (cNode.getEdge(index)).getNode(); // Reset current node to the next node in the path
        }
        return true; // Substring was found
    }

Specifically, am I allowed to set Node root = null as a class variable, initialise root when an object of type Trie is created and change cNode as shown in the methods? The code is compiled with no errors, however when tested it does not always output the correct response eg when tested, it outputs that 'es' is not a substring of 'pest'.

Updating fields in a method of a class makes a class not thread safe. Your methods have side effects that may not be what the user of the your class expects.

Consider:

 Trie t = new Trie("My String");

 boolean startsWithMy = t.substring("My");
 boolean startsWithMyString = t.substring("My String");

If you're updating the root field in the substring method, then the 2nd call won't do what you might expect, since the first substring call changed the Trie.

If you want to make a reusable class that is easy to use with minimal side effects, then what I would do is write your class following this basic pattern:

public class Trie {
     private final Node root;

     public Trie(String input) {
         // Construct the Trie here and assign it to root:
         this.root = constructTry(input);
     }

     public boolean substring(String part) {
         // Create a local Node variable:
         Node currentNode = root;

         // Navigate the Trie here using currentNode:
         // ...

         return result;
     }
}

You can even add a method (if you desire) to return a subpart of the Trie:

public Trie subTrie(String part) {
    // Find the Node here that matches the substring part, and return it.
    // If nothing found, then throw NoSuchElementException or return null.

    Node subNode = findNode(part);

    if (subNode == null) {
        throw new NoSuchElementException("No element starting with: " + part);
    }

    // Constructs a new Trie with a different root node using a 2nd constructor option
    return new Trie(subNode); 
}

You are changing the reference of your root node by adding garbage to it. Let say you do this:

 Trie trie = new Trie();
 trie.substring("es", "pest"); // this returns true. 

but if you do

 Trie trie = new Trie();
 trie.substring("es", "pest");  
 trie.substring("te", "Master"); 

You second call to Substring will pick up where your last call left. You root is already initialized and contains a tree for the word "pest" root(p, e, s, t, #) . After the second call instead of having as expected root(M, a, s, t, e, r, #) , you end up with root(p, e, s, t, #, M, a, r) . Which is is a completely different word. As such te is not a substring of pest#Mar .

But if you implement it according to @john16384, you will be forced to do the following which eliminate the side effects:

 Trie trie = new Trie("pest");
 trie.substring("es"); // this returns true. 

 trie = new Trie("Master");
 trie.substring("te") // this returns true. 

Doing it this way alway forces you to start from a clean root. See the implementation below:

 class Trie {

protected Node root = null;

public Trie(String T) {
    root = constructTrie(T);
}

// Constructs a trie for a given string T
private Node constructTrie(String T) {
    ArrayList<String> suffixArray = new ArrayList<String>();
    T += "#"; // Terminator
    int length = T.length();
    // Creates suffix array and removes first letter with every iteration
    for (int i = 0; i < length; i++) {
        suffixArray.add(T);
        T = T.substring(1);
    }
    Node localRoot = new Node();
    // Goes through suffix array
    for (int i = 0; i < length; i++) {
        Node cNode = localRoot;
        int j = 0;
        // Goes through each letter of an entry in the suffix array
        while (j < (suffixArray.get(i)).length()) {
            int index = cNode.findEdge((suffixArray.get(i)).charAt(j));
            // If an edge is found at the root with the current letter, update cNode and remove the letter from word
            if (index != -1) {
                cNode = cNode.getEdge(index).getNode(); // Gets node pointed at by edge and sets it as current node
                String replace = (suffixArray.get(i)).substring(1);
                suffixArray.set(0, replace); // Erases first letter of suffix
                j++;
                System.out.println(i + " " + j + " " + replace);
            }
            // If an edge is not found at the root, write the whole word
            else {
                for (int k = 0; k < (suffixArray.get(i)).length(); k++) {
                    Edge e = new Edge((suffixArray.get(i)).charAt(k)); // Creates edge with current letter of current entry of the suffix array
                    Node n = new Node(); // Creates node to be pointed at by edge
                    e.setNode(n);
                    cNode.newEdge(e);
                    cNode = n; // Updates current node
                }
                j = (suffixArray.get(i)).length(); // If the word is written, we break from the while and move on to the next suffix array entry
            }
        }
    }
    return localRoot;
}

// Checks if S is a substring of T
public boolean substring(String S) {
    Node cNode = root;
    int index;
    for (int i = 0; i < S.length(); i++) {
        index = cNode.findEdge(S.charAt(i));
        if (index == -1)
            return false; // Substring was not found because a path was not followed
        cNode = (cNode.getEdge(index)).getNode(); // Reset current node to the next node in the path
    }
    return true; // Substring was found
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM