/// <summary> /// Filters a tree to get the leaf node. /// </summary> /// <param name="node">The first node of the tree.</param> /// <param name="questions">A Dictionary to contains all the Question used by the tree.</param> /// <param name="label">The given label.</param> /// <returns>The leaf node which match the label.</returns> public static DecisionTreeNode FilterTree(DecisionTreeNode node, IDictionary<string, Question> questions, Label label) { while (node.NodeType != DecisionTreeNodeType.Leaf) { Question question = questions[node.QuestionName]; // If the value set of question contains the feature value, it means matched with question mark. if (MatchHtsQuestion(question.ValueSet, label.GetFeatureValue(question.FeatureName))) { node = node.RightChild; } else { node = node.LeftChild; } } return node; }
/// <summary> /// Compare the DecisionTreeNode, used by List.Sort() method. /// </summary> /// <param name="firstNode">Source DecisionTreeNode.</param> /// <param name="secondNode">Destination DecisionTreeNode.</param> /// <returns>Positive for x greater than y, 0 for equal and negative for x less than y.</returns> private static int Compare(DecisionTreeNode firstNode, DecisionTreeNode secondNode) { Helper.ThrowIfNull(firstNode); Helper.ThrowIfNull(secondNode); // give leaf node a very low ID to ensure leaf node was put in the end. int firstId = int.MinValue / 2; int secondId = int.MinValue / 2; if (firstNode.NodeType == DecisionTreeNodeType.NonLeaf) { firstId = int.Parse(firstNode.Name, CultureInfo.InvariantCulture); } if (secondNode.NodeType == DecisionTreeNodeType.NonLeaf) { secondId = int.Parse(secondNode.Name, CultureInfo.InvariantCulture); } return secondId - firstId; }
/// <summary> /// Load the decision tree. /// </summary> /// <param name="treeLines">Multiple lines to contain the tree information.</param> public void Load(Collection<string> treeLines) { Helper.ThrowIfNull(treeLines); // Verify treeLines' format if (!(treeLines.Count == 2 || (treeLines.Count >= 4 && treeLines[1].Equals(StartSymbol) && treeLines[treeLines.Count - 1].Equals(EndSymbol)))) { throw new InvalidDataException(Helper.NeutralFormat( "Tree \"{0}\" has invalidate format", treeLines[0])); } Dictionary<string, DecisionTreeNode> nameNodeMap = new Dictionary<string, DecisionTreeNode>(); Name = treeLines[0].Trim(); // Standard format if (treeLines.Count >= 4) { // add non-leaf node in the beginning for (int i = 2; i < treeLines.Count - 1; i++) { DecisionTreeNode node = new DecisionTreeNode(); _nodeList.Add(node); nameNodeMap.Add(NodeIdxToName(i - 2), node); } for (int i = 2; i < treeLines.Count - 1; i++) { string line = treeLines[i]; string[] infos = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); if (infos.Length != 4) { throw new InvalidDataException(Helper.NeutralFormat( "Tree node format should be \"Name Question NoChild YesChild\": {0}", line)); } Debug.Assert(infos[0].Equals(NodeIdxToName(i - 2))); // pick up non-leaf node DecisionTreeNode node = nameNodeMap[infos[0]]; node.Name = infos[0]; node.QuestionName = infos[1]; node.NodeType = DecisionTreeNodeType.NonLeaf; if (infos[2].IndexOf('-') == 0) { // non leaf node Debug.Assert(nameNodeMap.ContainsKey(infos[2])); DecisionTreeNode childNode = nameNodeMap[infos[2]]; node.LeftChild = childNode; childNode.Parent = node; } else { // leaf node Debug.Assert(infos[2].IndexOf(LeafNameChar) == 0); DecisionTreeNode leafNode = new DecisionTreeNode(); leafNode.Name = infos[2].Trim(LeafNameChar); leafNode.NodeType = DecisionTreeNodeType.Leaf; leafNode.Parent = node; node.LeftChild = leafNode; _leafNodeMap.Add(leafNode.Name, leafNode); _nodeList.Add(leafNode); } if (infos[3].IndexOf('-') == 0) { // non leaf node Debug.Assert(nameNodeMap.ContainsKey(infos[3])); DecisionTreeNode childNode = nameNodeMap[infos[3]]; node.RightChild = childNode; childNode.Parent = node; } else { // leaf node Debug.Assert(infos[3].IndexOf(LeafNameChar) == 0); DecisionTreeNode leafNode = new DecisionTreeNode(); leafNode.Name = infos[3].Trim(LeafNameChar); leafNode.NodeType = DecisionTreeNodeType.Leaf; leafNode.Parent = node; node.RightChild = leafNode; _leafNodeMap.Add(leafNode.Name, leafNode); _nodeList.Add(leafNode); } } } else { // one leaf node format DecisionTreeNode leafNode = new DecisionTreeNode(); leafNode.Name = treeLines[1].Trim(LeafNameChar); leafNode.NodeType = DecisionTreeNodeType.Leaf; _leafNodeMap.Add(leafNode.Name, leafNode); _nodeList.Add(leafNode); } }
/// <summary> /// Assign a id to certain node and its children. /// </summary> /// <param name="node">Tree node to visit.</param> /// <param name="seq">Node id sequence.</param> /// <returns>The next id.</returns> private static int TravelOneNode(DecisionTreeNode node, int seq) { if (node.NodeType == DecisionTreeNodeType.NonLeaf) { node.Name = NodeIdxToName(seq); seq++; seq = TravelOneNode(node.LeftChild, seq); seq = TravelOneNode(node.RightChild, seq); } return seq; }
/// <summary> /// PreOrder decision tree traversal. /// </summary> /// <param name="node">Start node for traversal.</param> /// <param name="visitor">Visitor funclet.</param> /// <returns>True if the visitor needs continue at current node; false otherwise.</returns> public static bool PreOrderVisit(DecisionTreeNode node, Func<DecisionTreeNode, bool> visitor) { if (node == null) { throw new ArgumentNullException("node"); } if (visitor == null) { throw new ArgumentNullException("visitor"); } var needContinue = visitor(node); if (needContinue && node.NodeType == DecisionTreeNodeType.NonLeaf) { needContinue = PreOrderVisit(node.LeftChild, visitor); } if (needContinue && node.NodeType == DecisionTreeNodeType.NonLeaf) { needContinue = PreOrderVisit(node.RightChild, visitor); } return needContinue; }