Print multi tree data structure ascii visualization on console with java

I was struggling with a debugging problem in one of my programs, which showed intermittent bugs when working on a huge amount of text data. The program was performing pattern matching using a tree prediction as discussed in my other blog at http://coffeefromme.blogspot.com/2015/10/to-extract-date-from-given-text-with.html

Since debugging huge set of text data is very hard, I required my tree to show on console visually to help me find the bug source. I tried to find available algorithms to print tree on the console. There were programs which could print binary trees, but not for the multi-children tree. So I created the following code to print the tree.

Sample Tree using text in class Test
Class : Test (To build and print the tree)

import java.util.ArrayList;
import java.util.List;


public class Test {

    public static void main(String a[]) {
        List<String> l = new ArrayList<String>();
        l.add("DDDD*DD*DD");
        l.add("DDDD*D*DD");
        l.add("DDDD*DD*D");
        l.add("DDDD*D*D");
        l.add("DD*DD*DD");
        l.add("DD*D*DD");
        l.add("DD*DD*D");
        l.add("DD*D*D");
        l.add("DD*DD*DDDD");
        l.add("D*DD*DDDD");
        l.add("DD*D*DDDD");
        l.add("D*D*DDDD");
        l.add("DD*DD*DD");
        l.add("D*DD*DD");
        l.add("DD*D*DD");
        l.add("D*D*DD");
        l.add("DDDD*M*DD");
        l.add("DDDD*M*D");
        l.add("DD*M*DD");
        l.add("DD*M*D");
        l.add("DD*M*DDDD");
        l.add("D*M*DDDD");
        l.add("DD*M*DD");
        l.add("D*M*DD");
        l.add("M*DD*DDDD");
        l.add("M*D*DDDD");
        l.add("M*DD*DD");
        l.add("M*D*DD");
        
        PredictionModelNode tree = new PredictionModelNode();
        Dictionary.buildTree(l, tree);
        Dictionary.printTree(tree);
    }
}


Class : PredictionModelNode


import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Vaibhav Singh
 * This class creates multi tree data structure
 */
public class PredictionModelNode {

    public char charcter;
    boolean isDigit = false;
    public int level;
    public List<PredictionModelNode> childern;
    public PredictionModelNode parent;
    public boolean explictDateFragment=false;

    public PredictionModelNode getChild(char c) {
        if (childern == null) {
            return null;
        }
        for (PredictionModelNode child : childern) {
            if (c == child.charcter) {
                return child;
            }
        }
        return null;
    }

    public void addChild(PredictionModelNode child) {
        if (childern == null) {
            childern = new ArrayList<PredictionModelNode>();
        }
        child.level=this.level+1;
        childern.add(child);
    }
    
    public boolean hasChildern(){
         if (childern == null) {
             return false;
         }else{
             return true;
         }
    }
    
    public int childrenCount(){
         if (childern == null) {
             return 0;
         }else{
             return childern.size();
         }
    }
    
    
} 


Class : TreeChar

/**
 * To create a bit map type structure for lietrals of the tree
 * @author Vaibhav Singh
 *
 */
public class TreeChar implements Comparable<TreeChar>{
    
    public String symbol="";
    
    public int leftSpace=0;
    
    public int level=0;

    public TreeChar(String symbol, int leftSpace, int level) {
        super();
        this.symbol = symbol;
        this.leftSpace = leftSpace;
        this.level = level;
    }

    public TreeChar(String symbol) {
        super();
        this.symbol = symbol;
    }

    @Override
    public int compareTo(TreeChar arg0) {
        if(level>arg0.level){
            return 1;
        }
        if(level<arg0.level){
            return -1;
        }
        return 0;
    }
}

Class : Dictionary

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * This class helps to build the tree data structure using PredictionModelNode class and helps to print it.
 * @author Vaibhav Singh
 */
public class Dictionary {

    /* only used to print tree on console */
    public static final int HOROZONTAL_PRINT_GAP = 3;
    public static final int VERTICAL_PRINT_GAP = 2;


    public static void buildTree(List<String> itemList, PredictionModelNode parentNode) {
        for (String s : itemList) {
            PredictionModelNode parent = parentNode;
            parentNode.charcter = 0;
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                PredictionModelNode node = parent.getChild(c);
                if (node == null) {
                    PredictionModelNode child = new PredictionModelNode();
                    child.charcter = c;
                    if (i + 1 == s.length()) {
                        child.explictDateFragment = true;
                    }
                    parent.addChild(child);
                    parent = child;
                } else {
                    if (i + 1 == s.length()) {
                        node.explictDateFragment = true;
                    }
                    parent = node;
                }
            }
        }
    }

    public static void printTree(PredictionModelNode tree) {

        tree = sortTree(tree);

        DisplayObject displyObject = displayBuilder(new DisplayObject(), tree);

        StringBuffer printBuffer = new StringBuffer();

        String line = getBlankLine(displyObject.width);

        int prevLevel = 0;
        for (TreeChar tc : displyObject.displayBuffer) {
            if (prevLevel == tc.level) {
                line = line.substring(0, tc.leftSpace) + tc.symbol + line.substring(tc.leftSpace + 1);
            } else {
                line = line.replaceAll("\\s+$", "") + System.lineSeparator();
                printBuffer.append(line);
                line = getBlankLine(displyObject.width);
                line = line.substring(0, tc.leftSpace) + tc.symbol + line.substring(tc.leftSpace + 1);
                prevLevel = tc.level;
            }
        }
        printBuffer.append(line);
        System.out.println(printBuffer.toString());

    }

    /**
     * 
     * @param disply
     * @param tree
     * @return
     */
    private static DisplayObject displayBuilder(DisplayObject display, PredictionModelNode tree) {
        if (isFamilyUnit(tree)) {
            return familyDisplayBuilder(tree);
        }

        DisplayObject horizontalDisply = new DisplayObject();
        for (PredictionModelNode kid : tree.childern) {
            DisplayObject recusiveDisplay = displayBuilder(horizontalDisply, kid);
            horizontalDisply = adjustHorizontalDisplay(horizontalDisply, recusiveDisplay);
        }

        Collections.sort(horizontalDisply.displayBuffer);

        DisplayObject verticalDisplay = new DisplayObject();
        int[] dadJoints = new int[tree.childrenCount()];

        int j = 0;
        for (TreeChar tc : horizontalDisply.displayBuffer) {
            if (tc.level > 0 || j > dadJoints.length) {
                break;
            }
            if (!(tc.symbol.matches("/|\\\\|_|\\|") || "".equals(tc.symbol.trim()))) {
                dadJoints[j++] = tc.leftSpace;
            }
        }

        if (tree.childrenCount() == 0) {
            return horizontalDisply;
        }
        if (tree.childrenCount() == 1) {
            TreeChar tc = new TreeChar(String.valueOf(tree.charcter), dadJoints[0], 0);
            verticalDisplay.displayBuffer.add(tc);

        } else {
            Arrays.sort(dadJoints);
            String symbol = "_";
            int papaPosition = dadJoints.length % 2 == 0 ? 1+(dadJoints[dadJoints.length - 1] + dadJoints[0]) / 2 : dadJoints[dadJoints.length / 2] + 1;
            // improve visualization
            papaPosition--;
            for (int k = dadJoints[0] + 1; k <= dadJoints[dadJoints.length - 1] + 1 - 2; k++) {
                TreeChar tc;
                if (k == papaPosition) {
                    String s = "".equals(String.valueOf(tree.charcter).trim()) ? "$" : String.valueOf(tree.charcter);
                    tc = new TreeChar(s, k, 0);
                } else {
                    tc = new TreeChar(symbol, k, 0);
                }
                verticalDisplay.displayBuffer.add(tc);
            }
        }

        for (int i = 0; i < dadJoints.length; i++) {
            for (int m = 0; m < VERTICAL_PRINT_GAP; m++) {
                TreeChar tc = new TreeChar("|", dadJoints[i], m + 1);
                verticalDisplay.displayBuffer.add(tc);
            }
        }

        display = adjustVerticalDisplay(horizontalDisply, verticalDisplay);

        return display;
    }

    private static DisplayObject adjustVerticalDisplay(DisplayObject horizontalDisply, DisplayObject verticalDisplay) {
        if (horizontalDisply.displayBuffer.size() == 0) {
            return verticalDisplay;
        }
        Collections.sort(verticalDisplay.displayBuffer);
        for (TreeChar tc : horizontalDisply.displayBuffer) {
            tc.level += VERTICAL_PRINT_GAP + 1;
            verticalDisplay.displayBuffer.add(tc);
        }
        verticalDisplay.width = horizontalDisply.width;
        return verticalDisplay;
    }

    /**
     * 
     * @param display
     * @param kidDisplay
     * @return
     */
    private static DisplayObject adjustHorizontalDisplay(DisplayObject display, DisplayObject kidDisplay) {
        if (display.displayBuffer.size() == 0) {
            return kidDisplay;
        }

        int leftPos = display.width;
        for (TreeChar tc : kidDisplay.displayBuffer) {
            tc.leftSpace += (leftPos + HOROZONTAL_PRINT_GAP);
            int i = 0;
            int displayBufferSize = display.displayBuffer.size();
            for (TreeChar displayTc : display.displayBuffer) {
                if (displayBufferSize == i + 1) {
                    display.displayBuffer.add(displayBufferSize, tc);
                    break;
                } else if (displayTc.level <= tc.level) {
                    i++;
                    continue;
                }
                display.displayBuffer.add(i - 1, tc);
                break;
            }
        }
        display.height = display.height >= kidDisplay.height ? display.height : kidDisplay.height;
        display.width = display.width + kidDisplay.width + HOROZONTAL_PRINT_GAP;
        return display;

    }

    private static DisplayObject familyDisplayBuilder(PredictionModelNode tree) {
        DisplayObject displyObject = new DisplayObject();
        int kids = tree.childrenCount();
        String charVal = String.valueOf(tree.charcter);
        if (kids == 1) {
            displyObject.displayBuffer.add(new TreeChar(charVal, 0, 0));
            displyObject.displayBuffer.add(new TreeChar("|", 0, 1));
            String kidChar = String.valueOf(tree.childern.get(0).charcter);
            displyObject.displayBuffer.add(new TreeChar(kidChar, 0, 2));
            displyObject.width = 1;
            displyObject.height = 3;
        } else if (kids % 2 == 0) {
            displyObject = evenKidCountTree(displyObject, tree);
        } else {
            displyObject = oddKidCountTree(displyObject, tree);
        }
        return displyObject;
    }

    /**
     * 
     * @param displayBuffer
     * @param tree
     * @return
     */
    public static DisplayObject evenKidCountTree(DisplayObject displyObject, PredictionModelNode tree) {
        int kids = tree.childrenCount();
        int papaPosition = kids / 2;
        String charVal = String.valueOf(tree.charcter);
        displyObject.displayBuffer.add(new TreeChar(charVal, papaPosition, 0));
        displyObject.height = papaPosition + 2;
        displyObject.width = kids + 1;
        // Iterate to add tree structure elements '/','|','\'
        for (int i = 0; i < kids / 2; i++) {
            // Iterate to add columns
            int markers = (i + 1) * 2;
            int coeffiecient = 1;
            for (int j = 0; j < markers; j++) {
                // reset the coefficient once halfway is crossed
                if (coeffiecient == markers / 2 + 1) {
                    coeffiecient = 1;
                }
                // half of markers will be on left
                if (j < markers / 2) {
                    // String symbol = "/";
                    String symbol = "|";
                    int leftpos = papaPosition - coeffiecient++;
                    /*
                     * if (leftpos == papaPosition - 1 && i != 0) { symbol =
                     * "|"; }
                     */
                    if (j == markers / 2 - 1) {
                        symbol = "/";
                    }
                    displyObject.displayBuffer.add(new TreeChar(symbol, leftpos, i + 1));
                } else {
                    int rightpos = papaPosition + coeffiecient++;
                    // String symbol = "\\";
                    String symbol = "|";
                    /*
                     * if (rightpos == papaPosition + 1 && i != 0) { symbol =
                     * "|"; }
                     */
                    if ((j == markers - 1 && i != 0) || markers < 3) {
                        symbol = "\\";
                    }
                    displyObject.displayBuffer.add(new TreeChar(symbol, rightpos, i + 1));
                }
            }

        }
        // Iterate to add children to tree
        for (int i = 0; i < kids; i++) {
            String kidVal = String.valueOf(tree.childern.get(i).charcter);
            if (i < kids / 2) {
                displyObject.displayBuffer.add(new TreeChar(kidVal, i, kids / 2 + 1));
            } else {
                displyObject.displayBuffer.add(new TreeChar(kidVal, i + 1, kids / 2 + 1));
            }
        }
        return displyObject;
    }

    /**
     * 
     * @param displayBuffer
     * @param tree
     * @return
     */
    public static DisplayObject oddKidCountTree(DisplayObject displyObject, PredictionModelNode tree) {
        int kids = tree.childrenCount();
        int papaPosition = (kids - 1) / 2;
        String charVal = String.valueOf(tree.charcter);
        displyObject.displayBuffer.add(new TreeChar(charVal, papaPosition, 0));
        displyObject.height = papaPosition + 2;
        displyObject.width = kids;
        // Iterate to add tree structure elements '/','|','\'
        for (int i = 0; i < (kids - 1) / 2; i++) {
            // Iterate to add columns
            int markers = 2 * i + 3;
            int coeffiecient = 1;
            for (int j = 0; j < markers; j++) {
                // reset the coefficient once halfway is crossed
                if (coeffiecient == markers / 2 + 1) {
                    coeffiecient = 1;
                }
                // half of markers will be on left
                if (j < markers / 2) {
                    // String symbol = "/";
                    String symbol = "|";
                    int leftpos = papaPosition - coeffiecient++;
                    /*
                     * if (leftpos == papaPosition - 1 && i != 0) { symbol =
                     * "|"; }
                     */
                    if (j == markers / 2 - 1 && markers > 2) {
                        symbol = "/";
                    }
                    displyObject.displayBuffer.add(new TreeChar(symbol, leftpos, i + 1));
                } else if (j == markers / 2) {
                    displyObject.displayBuffer.add(new TreeChar("|", papaPosition, i + 1));
                } else {
                    int rightpos = papaPosition + coeffiecient++;
                    // String symbol = "\\";
                    String symbol = "|";
                    /*
                     * if (rightpos == papaPosition + 1 && i != 0) { symbol =
                     * "|"; }
                     */
                    if (j == markers - 1 && markers > 2) {
                        symbol = "\\";
                    }
                    displyObject.displayBuffer.add(new TreeChar(symbol, rightpos, i + 1));
                }
            }

        }
        // Iterate to add children to tree
        for (int i = 0; i < kids; i++) {
            String kidVal = String.valueOf(tree.childern.get(i).charcter);
            displyObject.displayBuffer.add(new TreeChar(kidVal, i, kids / 2 + 1));
        }
        return displyObject;
    }

    private static int getMaxTreeHeight(PredictionModelNode tree) {
        if (tree.hasChildern()) {
            int[] height = new int[tree.childrenCount()];
            for (int i = 0; i < tree.childrenCount(); i++) {
                PredictionModelNode child = tree.childern.get(i);
                height[i] = getMaxTreeHeight(child);
            }
            int max = height[0];
            for (int j = 1; j < height.length; j++) {
                if (max < height[j]) {
                    max = height[j];
                }
            }
            return max + 1;
        }
        return 0;
    }

    private static PredictionModelNode sortTree(PredictionModelNode tree) {
        if (tree.hasChildern()) {
            for (PredictionModelNode child : tree.childern) {
                child = sortTree(child);
            }
            boolean swapped = true;
            if (tree.childrenCount() <= 1) {
                return tree;
            }
            while (swapped) {
                swapped = false;
                for (int i = 0; i < tree.childrenCount() - 1; i++) {
                    int thisHeight = getMaxTreeHeight(tree.childern.get(i));
                    int nextHeight = getMaxTreeHeight(tree.childern.get(i + 1));
                    if (thisHeight < nextHeight) {
                        swapped = true;
                        PredictionModelNode tempChild = tree.childern.get(i);
                        tree.childern.set(i, tree.childern.get(i + 1));
                        tree.childern.set(i + 1, tempChild);
                    }
                }
            }
            return tree;
        }
        return tree;
    }

    /**
     * 
     * @param tree
     * @return
     */
    private static boolean isFamilyUnit(PredictionModelNode tree) {
        if (tree.hasChildern()) {
            for (PredictionModelNode node : tree.childern) {
                if (node.hasChildern()) {
                    return false;
                }
            }
            return true;
        }
        return true;
    }

    private static String getBlankLine(int i) {
        String str = new String();
        str = " ";
        for (int j = 0; j < i; j++) {
            str = " " + str;
        }
        return str;
    }

}

Comments

  1. wow, its brilliant and helps a lot in debugging complex tree structures.

    ReplyDelete

Post a Comment

Popular posts from this blog

Caused by: java.sql.SQLTimeoutException: ORA-01013: user requested cancel of current operation

HashiCorp Vault Integration with Ansible Etower using approle

utility to extract date from text with java