Untitled
Anonymous
plain_text
03/01/2026 5:52 AM
9.6 KB
16
Indexable
// -------------------------------------------------------------------------
/**
* An internal node in the DNA tree.
* Has five children corresponding to A, C, G, T, and $.
* Internal nodes store no sequence data, only child pointers.
*
* @author Ishita Punna
* @author Leguejou Awunganyi
* @version Spring 2026
*/
public class InternalNode implements DNATreeNodes {
/** Number of children for each internal node (A, C, G, T, $). */
private static final int NUM_CHILDREN_FOR_INTERAL = 5;
/** Index for the A branch. */
private static final int DNA_A = 0;
/** Index for the C branch. */
private static final int DNA_C = 1;
/** Index for the G branch. */
private static final int DNA_G = 2;
/** Index for the T branch. */
private static final int DNA_T = 3;
/** Index for the $ branch. */
private static final int TERMINATING_CHAR = 4;
/** The indentation increment used during print. */
private static final String INDENT_STEP = " ";
/** Children array: indices 0=A, 1=C, 2=G, 3=T, 4=$. */
private DNATreeNodes[] children;
// ----------------------------------------------------------
/**
* Construct a new InternalNode with all children set to flyweight.
*/
public InternalNode() {
children = new DNATreeNodes[NUM_CHILDREN_FOR_INTERAL];
Flyweight fw = Flyweight.getInstance();
for (int i = 0; i < NUM_CHILDREN_FOR_INTERAL; i++) {
children[i] = fw;
}
}
// ----------------------------------------------------------
/**
* Get the child index for the character at depth in sequence.
* Uses $ (index 4) if depth equals the sequence length.
*
* @param sequence
* The DNA sequence
* @param depth
* The current depth
* @return Child index 0-4
*/
private int childIndex(String sequence, int depth) {
if (depth >= sequence.length()) {
return TERMINATING_CHAR;
}
char c = sequence.charAt(depth);
if (c == 'A') {
return DNA_A;
}
else if (c == 'C') {
return DNA_C;
}
else if (c == 'G') {
return DNA_G;
}
else if (c == 'T') {
return DNA_T;
}
else {
// Should never reach here with valid input
return TERMINATING_CHAR;
}
}
// ----------------------------------------------------------
/**
* Insert a sequence into the subtree rooted here.
*
* @param sequence
* The sequence to insert
* @param depth
* Current depth in the sequence
* @return This internal node (unchanged root)
*/
@Override
public DNATreeNodes insert(String sequence, int depth) {
int dnaIndex = childIndex(sequence, depth);
children[dnaIndex] = children[dnaIndex].insert(sequence, depth + 1);
return this;
}
// ----------------------------------------------------------
/**
* Remove a sequence from the subtree rooted here.
* After removal, if only one non-flyweight leaf child remains,
* collapse this internal node back to that leaf.
*
* @param sequence
* The sequence to remove
* @param depth
* Current depth
* @return The (possibly collapsed) node
*/
@Override
public DNATreeNodes remove(String sequence, int depth) {
int dnaIndex = childIndex(sequence, depth);
children[dnaIndex] = children[dnaIndex].remove(sequence, depth + 1);
Leaf singleLeaf = null;
int nonEmptyCount = 0;
for (int i = 0; i < NUM_CHILDREN_FOR_INTERAL; i++) {
if (!children[i].isFlyweight()) {
nonEmptyCount++;
if (children[i].isLeaf()) {
singleLeaf = (Leaf) children[i];
}
else {
return this;
}
}
}
if (nonEmptyCount != 1) {
return this;
}
return singleLeaf;
}
// ----------------------------------------------------------
/**
* Search for sequences matching the given prefix.
* Counts this node as visited then recurses into
* the relevant subtree(s).
*
* @param prefix
* The search prefix (may end with $)
* @param depth
* Current depth in the prefix
* @param result
* The accumulator
*/
@Override
public void search(String prefix, int depth, SearchResult result) {
result.visit();
boolean exactSearch = prefix.endsWith("$");
// Effective prefix length without the trailing $
int prefixLen = exactSearch ? prefix.length() - 1 : prefix.length();
if (depth < prefixLen) {
// Still consuming prefix characters - go into one child
char c = prefix.charAt(depth);
int dnaIndex;
if (c == 'A') {
dnaIndex = DNA_A;
}
else if (c == 'C') {
dnaIndex = DNA_C;
}
else if (c == 'G') {
dnaIndex = DNA_G;
}
else if (c == 'T') {
dnaIndex = DNA_T;
}
else {
dnaIndex = TERMINATING_CHAR;
}
children[dnaIndex].search(prefix, depth + 1, result);
}
else if (exactSearch) {
// Exact search: depth == prefixLen, go into $ branch
children[TERMINATING_CHAR].search(prefix, depth + 1, result);
}
else {
// Prefix fully consumed: search all children
for (int i = 0; i < NUM_CHILDREN_FOR_INTERAL; i++) {
children[i].search(prefix, depth, result);
}
}
}
// ----------------------------------------------------------
/**
* Print this internal node as "I" then recurse into all children.
*
* @param indent
* The current indentation
* @param mode
* 0=plain, 1=lengths, 2=stats
* @param sb
* The StringBuilder to append to
*/
@Override
public void print(String indent, int mode, StringBuilder sb) {
sb.append(indent).append("I\r\n");
String childIndent = indent + INDENT_STEP;
for (int i = 0; i < NUM_CHILDREN_FOR_INTERAL; i++) {
children[i].print(childIndent, mode, sb);
}
}
// ----------------------------------------------------------
/**
* Returns false because this is an internal node, not flyweight.
*
* @return false
*/
@Override
public boolean isFlyweight() {
return false;
}
// ----------------------------------------------------------
/**
* Returns false because this is an internal node, not a leaf.
*
* @return false
*/
@Override
public boolean isLeaf() {
return false;
}
}Editor is loading...
Leave a Comment