package org.baderlab.csplugins.brainlib;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
/**
* HierarchicalClusteringResultTree structure capturing the output of a hierarchical clustering algorithm (for convenience)
* Unique labels that correspond to the original data are stored in the tree and should be used to map back to the original data
*/
public class HierarchicalClusteringResultTree {
public HierarchicalClusteringResultTree left; //left child
public HierarchicalClusteringResultTree right; //right child
public int nodeIndex; //node number for non leaves, original data index for leaves
public double distance; //For internal node: the cluster distance these children were joined at
public double leafOrderIndex; //For leaf node: the order to position the leaf at for output
public boolean leaf; //true for leaf node, false for internal node
public int numberOfLeaves; //For internal node: the number of leaves that this node is an ancestor of
public String leafLabel; //the label of a leaf (could be null)
public List flatLeftChildrenLeaves; //a flat list of all leaves that are children of the left child node
public List flatRightChildrenLeaves; //a flat list of all leaves that are children of the right child node
public HierarchicalClusteringResultTree leftLeaf; //index of the joined leaf under the left child
public HierarchicalClusteringResultTree rightLeaf; //index of the joined leaf under the right child
/**
* Create a leaf node
*
* @param leafIndex The index of the leaf in the original data
* @param leafOrderIndex The order the leaf should be drawn
*/
public HierarchicalClusteringResultTree(int leafIndex, int leafOrderIndex, String label) {
this.nodeIndex = leafIndex;
this.distance = 0.0;
this.leafOrderIndex = leafOrderIndex;
leaf = true;
this.numberOfLeaves = 1;
this.leafLabel = label;
}
/**
* Create an internal node
*
* @param left The left child
* @param right The right child
* @param nodeNum The index of the node
* @param distance The distance these nodes are joined at
*/
public HierarchicalClusteringResultTree(HierarchicalClusteringResultTree left, HierarchicalClusteringResultTree right, int nodeNum, double distance) {
if ((left == null) || (right == null)) {
throw new RuntimeException("Null passed to tree constructor");
}
this.left = left;
this.right = right;
this.nodeIndex = nodeNum;
this.distance = distance;
//the leaf order index of an internal node is the average of its children
this.leafOrderIndex = (left.leafOrderIndex + right.leafOrderIndex) / 2;
leaf = false;
this.numberOfLeaves += (left.numberOfLeaves + right.numberOfLeaves);
//maintain a flat list of labels for
flatLeftChildrenLeaves = new ArrayList();
getListOfChildrenLeaves(left, flatLeftChildrenLeaves);
flatRightChildrenLeaves = new ArrayList();
getListOfChildrenLeaves(right, flatRightChildrenLeaves);
}
/**
* Create an internal node
*
* @param left The left child
* @param right The right child
* @param nodeNum The index of the node
* @param distance The distance these nodes are joined at
*/
public HierarchicalClusteringResultTree(HierarchicalClusteringResultTree left, HierarchicalClusteringResultTree right, int nodeNum, double distance,
HierarchicalClusteringResultTree leftLeaf, HierarchicalClusteringResultTree rightLeaf) {
if ((left == null) || (right == null)) {
throw new RuntimeException("Null passed to tree constructor");
}
this.left = left;
this.right = right;
this.nodeIndex = nodeNum;
this.distance = distance;
this.leftLeaf = leftLeaf;
this.rightLeaf = rightLeaf;
//the leaf order index of an internal node is the average of its children
this.leafOrderIndex = (left.leafOrderIndex + right.leafOrderIndex) / 2;
leaf = false;
this.numberOfLeaves += (left.numberOfLeaves + right.numberOfLeaves);
//maintain a flat list of labels for
flatLeftChildrenLeaves = new ArrayList();
getListOfChildrenLeaves(left, flatLeftChildrenLeaves);
flatRightChildrenLeaves = new ArrayList();
getListOfChildrenLeaves(right, flatRightChildrenLeaves);
}
/**
* Helper method to maintain a list of children leaves at each node
*
* @param child The child to collect the leaves from
* @param leafList The leaf list to maintain
*/
private void getListOfChildrenLeaves(HierarchicalClusteringResultTree child, List leafList) {
if (child.leaf) {
leafList.add(child);
} else {
//this assumes that the tree is built from the leaves up towards the root
leafList.addAll(child.flatLeftChildrenLeaves);
leafList.addAll(child.flatRightChildrenLeaves);
}
}
/**
* Checks if two HierarchicalClusteringResultTree objects are equal.
*
* @return True if their children are equal (irrespective of direction of the children)
*/
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof HierarchicalClusteringResultTree)) return false;
final HierarchicalClusteringResultTree that = (HierarchicalClusteringResultTree) obj;
List objLeftChildren = that.flatLeftChildrenLeaves;
List objRightChildren = that.flatRightChildrenLeaves;
if (this.leaf == true && that.leaf == true) {
//both leaves, ok to compare
//leaves are equal if they have the same label (can't compare indices, since these are likely different
//across different clusterings
if (this.leafLabel == null || that.leafLabel == null) {
throw new RuntimeException("Labels must be defined for the cluster results in order to determine equality.");
}
if (this.leafLabel.equalsIgnoreCase(that.leafLabel)) {
return true;
}
return false;
} else if (this.leaf == false && that.leaf == false) {
//both internal nodes, ok to compare
boolean leftEqual = false;
boolean rightEqual = false;
//check if left child is in other node
if (this.flatLeftChildrenLeaves.containsAll(objLeftChildren) && (objLeftChildren.containsAll(this.flatLeftChildrenLeaves))) {
leftEqual = true;
} else
if (this.flatLeftChildrenLeaves.containsAll(objRightChildren) && (objRightChildren.containsAll(this.flatLeftChildrenLeaves))) {
leftEqual = true;
}
//check if right child is in other node
if (this.flatRightChildrenLeaves.containsAll(objLeftChildren) && (objLeftChildren.containsAll(this.flatRightChildrenLeaves))) {
rightEqual = true;
} else
if (this.flatRightChildrenLeaves.containsAll(objRightChildren) && (objRightChildren.containsAll(this.flatRightChildrenLeaves))) {
rightEqual = true;
}
if (leftEqual && rightEqual) {
return true;
}
}
return false;
}
/**
* Creates a hashcode based on the labels present in each left and right child
*
*/
public int hashCode() {
int result;
String concatenatedLeafLabelsLeft = concatenateLeafLabelsInOrder(flatLeftChildrenLeaves);
String concatenatedLeafLabelsRight = concatenateLeafLabelsInOrder(flatRightChildrenLeaves);
result = (concatenatedLeafLabelsLeft != null ? concatenatedLeafLabelsLeft.hashCode() : 0);
result = 31 * result + (concatenatedLeafLabelsRight != null ? concatenatedLeafLabelsRight.hashCode() : 0);
return result;
}
/**
* Helper method for hashCode - creates a concatenated list of ordered leaf labels
*/
private String concatenateLeafLabelsInOrder(List childrenLeaves) {
TreeSet sortedLabels = new TreeSet();
StringBuffer sb = new StringBuffer();
//sort the leaf labels
for (int i = 0; i < childrenLeaves.size(); i++) {
HierarchicalClusteringResultTree node = (HierarchicalClusteringResultTree) childrenLeaves.get(i);
sortedLabels.add(node.leafLabel);
}
//concatenate the leaf labels in sorted order
for (Iterator iterator = sortedLabels.iterator(); iterator.hasNext();) {
String leafLabel = (String) iterator.next();
sb.append(leafLabel);
}
return sb.toString();
}
}