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.
Class : Test (To build and print the tree)
Class : PredictionModelNode
Class : TreeChar
Class : Dictionary
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 |
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; } }
wow, its brilliant and helps a lot in debugging complex tree structures.
ReplyDelete