Home > Software engineering >  Populate binary decision tree from indented file in C#
Populate binary decision tree from indented file in C#

Time:01-23

I have nested if-else statements generated by the D4.5 algorithm from a dataset in python. I want to transform this into a binary decision tree in Unity C# so I can traverse through it with my NPCs to create simple data-driven AI.

This is my input (currently indented by tabs but I can change it to a sequence of chars or just a number which tells me what level I am currently at):

HP is > 0: 
    SeesEnemy is False: 
        HearEnemy is False:
            Idle
        HearEnemy is True:
            Seeking
    SeesEnemy is True:
        EnemyInRange is True:
            Attacking
        EnemyInRange is False:
            Chasing
HP is <= 0:
    Dead

And I want Tree like this with negative child on left and positive on right: Tree

I do not have a problem with the implementation or traversing a tree but with the creation of it from data.

Another variant would be to transform input to this format, which I can deserialize to desired tree: "HP > 0?,Dead,#,#,SeesEnemy?,HearEnemy?,Idle,#,#,Seeking,#,#,EnemyInRange?,Chasing,#,#,Attacking,#,#" Where # means there is no child on left side and #,# means there are no children at all. This could be ideally done on python side.

I tried to read the input line by line while the number of tabs at the start of the line was incrementing like in the Depth-first search. My idea was to create a child of a current node on the left or right side based on false/true (<=/>) and return to the parent when the indentation of the next line was smaller than the previous one and continue with another side. But there was a problem with pointing to the current node.

I also tried to parse the file in levels (level 0 was "HP is > 0" and "HP is <= 0" etc.) but there were other problems which I could not solve.

I think there is some elegant recursion way to do this but I cannot find it nor figure it out itself. Thanks.

CodePudding user response:

Instead of building Data Structure Tree and then make a decision traversing, you can build it through expressions. Straight with your boolean conditions and actions and lazy execution of branches. Just traverse your file and build it through expression tree iterator:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/

Then, when you got your final expression you can just invoke (compile and invoke) and it will give you result. I built several DSL on this for my work, which are more complex (with bodies, loops, etc) than your case, so you should be fine.

If you struggle with parsing/traversing you can read more about bottom-up parsing on wiki - https://en.wikipedia.org/wiki/Bottom-up_parsing

To say it simple, you just create stack of simple expressions out of you file (usually constants or simple boolean conditions) and as you go through file, when something complete formed at the tail you transform tail (one or more elements) to next expression, then continue until you parsed entire file.

CodePudding user response:

Here is a way to create a tree, using a stack while reading the input string

import re

class Node:
    def __init__(self, data, condition=None):
        self.data = data
        self.condition = condition
        self.left = self.right = None

    def add(self, data, test):
        node = Node(data, test)
        if not self.right and self.condition != "False":
            self.right = node
        else:
            self.left = node
        if self.condition in ("False", "True"):
            self.condition = ""
        return node
    
    def preorder(self):
        if self:
            yield self.data   (" "   self.condition if self.condition else "")   ("?" if self.condition is not None else "")
            yield from Node.preorder(self.left)
            yield from Node.preorder(self.right)
        else:
            yield "#"

def tree(s):
    stack = [(-1, Node(None))]
    nodedepth = -1
    for match in re.finditer(r"([ ]*)(\S )(?: is (.*?):)?[ ]*$", s, re.M):
        depth = len(match[1])
        while depth <= stack[-1][0]:
            nodedepth, node = stack.pop()
        parent = stack[-1][1]
        stack.append((depth, node if nodedepth == depth else parent.add(match[2], match[3])))
    return stack[0][1].right

The tree function makes the tree from a string. The preorder method can be used to generate the serialized output string in the format you gave (with the hashes).

Example run:

s = """HP is > 0: 
    SeesEnemy is False: 
        HearEnemy is False:
            Idle
        HearEnemy is True:
            Seeking
    SeesEnemy is True:
        EnemyInRange is True:
            Attacking
        EnemyInRange is False:
            Chasing
HP is <= 0:
    Dead"""

root = tree(s)
print(",".join(root.preorder()))

Output:

HP > 0?,Dead,#,#,SeesEnemy?,HearEnemy?,Idle,#,#,Seeking,#,#,EnemyInRange?,Chasing,#,#,Attacking,#,#
  • Related