Populate binary decision tree from indented file in C# - python

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.

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.

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

BFS binary tree that implements Stack in Python

I am looking for implementing BFS (Breadth-First Search) of binary tree in Python using Stack (not Queue!).
class Stack:
def __init__(self):
self.data = []
def Empty(self):
return self.data == []
def Push(self, x):
self.data.append(x)
def Pop(self):
return self.data.pop()
def Peek(self):
return self.data[len(self.data)-1]
def Size(self):
return len(self.data)
class Node:
def __init__(self, data, left, right):
self.data = data
self.l_node = left
self.r_node = right
class Tree:
def __init__(self):
self.root= None
def bfs_stack(self, node):
pass
t = Tree()
t.root = Node("1")
t.root.l_node = Node("2")
t.root.l_node.l_node = Node("4")
t.root.l_node.r_node = Node("5")
t.root.l_node.r_node.l_node = Node("8")
t.root.r_node = Node("3")
t.root.r_node.l_node = Node("6")
t.root.r_node.r_node = Node("7")
t.root.r_node.r_node.l_node = Node("9")
t.root.r_node.r_node.l_node.l_node = Node("11")
t.root.r_node.r_node.r_node = Node("10")
I created Stack() class to work with.
I tried to combine recursion and stack pushing but I'm out of ideas.
Queue implementation is very easy but it's important for me to have a Stack() implementation.
Algorithmically, a BFS is tied to queue operations and DFS is tied to stack operations.
If you only have stacks though, you can form something like a queue out of two stacks.
Stacks implement a FILO (first-in,last-out) order, whereas queues implement FIFO (first-in, first out).
If you put three numbers, 1,2,3 in a stack, and pull them out, you come up with 3,2,1.
If you did the same thing with a queue, they would come out as 1,2,3.
Now suppose we put 1,2,3 in stack A, pulled them out as 3,2,1, and put them in stack B. Taking them out of stack B would yield them as 1,2,3, which means that going through two stacks resembles going through a queue.
Now this argument by itself works if you have the whole sequence and input everything all at once and then take it all out. But for BFS/DFSsearches this is not true, you input some things, then take them out before you input more.
You can still make a BFS work by only moving things from stack A to stack B if stack B is totally empty, and at that point transferring the entirety of stack A into B. B being empty ensures things that are put into stack B are all processed before the things being collected in stack A. Putting the entirety of A into B ensures all nodes at the same distance from the center node are kept in the same stack (either A or B) at any given time. Every transfer of content from stack A to B represents being done processing a BFS level at a particular distance from the start node, and inner layers are guaranteed to process before outer layers, which is what a BFS is.
stack_a = Stack()
stack_b = Stack()
stack_b.Push(t.root)
while len(stack_a)+len(stack_b)>0:
if len(stack_b) >0:
current_node = stack_b.Pop()
else:
while len(stack_a)> 0:
stack_b.Push(stack_a.Pop())
current_node = stack_b.Pop()
process(current_node) # this gets called on nodes in BFS order
for neighbor in [current_node.l_node, current_node.r_node]:
stack_a.Push(neighbor)
#can generalize this to non-binary graphs by iterating through all unvisited neighbors
#for cyclical graphs, must track processed nodes to ensure nodes don't get processed more than once

A* (A star) search algorithm implementation still not working

I haven't gotten a response on my other post since I updated it and I really need some help, so I'm reposting. The code below is the important bit, but a link to the full code is here: https://github.com/amstrudy/nao-ncsu/blob/master/oop_a_star.py
I've been following along the Wikipedia pseudocode for an A * implementation in Python. My code reaches the goal, but I can't seem to figure out how to reconstruct the actual path. It doesn't work because the piece of code where cameFrom is updated is only updated once at the end when the goal is reached. I don't understand that really but it's how the wikipedia article had the pseudocode.
def a_star ():
# create node object for home
home_node = Node(home, goal, home)
# set of nodes already evaluated
closedSet = []
# set of currently discovered nodes that are not evaluated yet
# initially, only the start node is known
openSet = [home_node]
while len(openSet) != 0:
minIndex = 0;
for i in list(range(len(openSet))):
if openSet[i] < openSet[minIndex]:
minIndex = i
cur_node = openSet[minIndex] # expand on node with smallest fScore
cur_node.generateNeighbors() # make new nodes to check
if cur_node.location == home_node.goal:
print("Made it to the goal!")
return reconstruct(cur_node)
openSet.pop(minIndex)
closedSet.append(cur_node)
for neighbor in cur_node.neighbors:
if neighbor in closedSet:
continue
if neighbor not in openSet:
openSet.append(neighbor)
if neighbor.gScore >= cur_node.gScore:
print(neighbor.gScore)
print(cur_node.gScore)
continue # this is not eh better path
# this path is the best for now, so record it
neighbor.cameFrom = cur_node
print(cur_node)
printMap(cur_node.location)
def reconstruct(target):
path = []
while target:
print("here")
path.append(target.location)
target = target.cameFrom
return path
For those commenting that this is a duplicate: yeah it is. I'm not getting a response on the old post, so I edited it and reposted. I will delete the old post.

Recursion and Binary Trees

#Get length of the longest path through recursion
def max_height(node):
if not node:
return 0
left = max_height(node.left) #Base Case based on my understanding
right = max_height(node.right) #Base Case based on my understanding
return max_height(left, right) + 1
I keep calling the max_height to get the length but I'm getting an error. I've thought of three possibilities:
1) I misunderstand the concept of the base case and I don't actually have a base case.
2) I'm not properly spacing Python code.
3) I'm not recursively getting the height of the BST at all but rather the width of the tree, which is affecting later calculations.
I know it is similar to this question, but the main difference is that I'm really trying to use recursion , where the other question used iteration and merely called it recursion.
how to find the height of a node in binary tree recursively
The base case is where the recursion stops and you have one: not node (node == None)
I don't see an issue with the spacing... Make sure you use only tabs or only spaces
This does produce the height: the number of nodes from root to leaf along the longest root-leaf path. At every node level, you add 1, and follow the higher subtree.
def max_height(node):
if not node: # this is the base case:
return 0 # where all recursive calls eventually stop
left = max_height(node.left) # <- these are the recursive calls:
right = max_height(node.right) # <- function x is called inside function x
return max(left, right) + 1 # max here, not max_height
Note that this is merely a more verbose version of this answer to the question you linked.
All answered were right but, I faced little problem while writing inside the class;
So, the code goes like this, I hope this helps.
class Tree(object):
def height(self, root):
if root == None: #root == None
return 0
else:
return 1 + max(self.height(root->left), self.height(root->left))
t = Tree()
t.height(t)

Tree Building Logic Trouble

I am writing a simple app that incorporates a tree that represents the English language. I have done something similar in C++, but this is my first venture at building a tree in Python.
englishWords = []
englishFile = open("english.txt")
for line in englishFile:
englishWords.append(line.rstrip())
class Node:
def __init__(self, value):
self.Value = value
self.checked = False
self.Pointers = []
self.numPointers = 0
def addNode(self, value):
x = Node(value)
self.Pointers.append(x)
return x
headString = "HEAD"
Head = Node(headString)
def buildPointers(parent, info, nodeList):
x = 0
y = len(nodeList)
while x < y :
if parent.numPointers == 0:
newNode = parent.addNode(info)
parent.numPointers = parent.numPointers + 1
buildPointers(newNode, nodeList[x+1], nodeList)
break
else:
for i in parent.Pointers:
if info == i.Value:
buildPointers(i, nodeList[x+1], nodeList)
continue
else:
newNode = parent.addNode(info)
parent.numPointers = parent.numPointers + 1
buildPointers(newNode, nodeList[x+1], nodeList)
continue
def treeBuild(lyst):
for i in lyst:
iList = list(i)
buildPointers(Head, iList[0], iList)
treeBuild(englishWords)
As soon as I run the code Windows says "python.exe has stopped running" it's probably something simple that I have overlooked, so feel free to rip into me or the way I wrote this. I would love any criticism that will help make me a better programmer.
Basically this isn't really pythonic, numerous errors here but I guess the main issue would be using too much recursion, something python "out of the box" isn't very good at.
It limits default recursion depth to 1000 steps. and you probably need more. here is a question and answer explaining how to change this default.
also another good advice would be changing the recursion to use generators like in this blog post
p.s: since you don't change the value of x the while loop might run forever in some cases wouldn't it? (I didn't fully understand the algorithm so I'm not sure)
EDIT: to make this a little more pythonic I would change the populating part to use with context manager:
with open("english.txt") as english_file:
for line in english_file ..
BTW a much better way, not loading million strings into a list would be changing the populating part to a generator function, yielding an english word everytime - much more efficient and pythonic. you can read about context managers and generator functions here
Another edit: learining idiomatic python The best place to start would be opening a python shell and:
import this
the "zen of python" would appear.
a good opinionated guide to modern python development including libs, best practice, reading recommendations and writing idomatic python would be Hitchhikers guide to python by kenneth reitz.
and a similar source, more focused one, is writing idiomatic Python
good luck!
You're not actually reducing the nodeList when you recurse causing the infinite recursion. You will also not break out of the loop properly when you finish processing a word. The following buildnodelist completes at the very least. I won't guarantee that it works as desired though since I only modified some blocking lines:
def buildPointers(parent, info, nodeList):
if parent.numPointers == 0:
newNode = parent.addNode(info)
parent.numPointers = parent.numPointers + 1
if len(nodeList) > 1:
buildPointers(newNode, nodeList[x+1], nodeList[1:])
else:
for i in parent.Pointers:
if info == i.Value:
if len(nodeList) > 1:
buildPointers(i, nodeList[x+1], nodeList[1:])
else:
newNode = parent.addNode(info)
parent.numPointers = parent.numPointers + 1
if len(nodeList) > 1:
buildPointers(newNode, nodeList[x+1], nodeList[1:])
Essentially, I've removed the While loop and passed slices of the 1st element of nodeList if it has more than 1 item in it.

Python: traverse tree adding html list (ul)

I have this python code that will traverse a tree structure. I am trying to add ul and li tags to the function but I am not very succesful. I though I was able to keep the code clean without to many conditionals but now I ain't so sure anymore.
def findNodes(nodes):
def traverse(ns):
for child in ns:
traverse.level += 1
traverse(child.Children)
traverse.level -= 1
traverse.level = 1
traverse(nodes)
This is the base function I have for traversing my tree structure. The end result should be nested ul and li tags. If need I can post my own not working examples but they might be a little confusing.
Update: Example with parameter
def findNodes(nodes):
def traverse(ns, level):
for child in ns:
level += 1
traverse(child.Children, level)
level -= 1
traverse(nodes, 1)
I removed the unused level parameter. Adding in any sort of text is left as an exercise to the reader.
def findNodes(nodes):
def traverse(ns):
if not ns:
return ''
ret = ['<ul>']
for child in ns:
ret.extend(['<li>', traverse(child.Children), '</li>'])
ret.append('</ul>')
return ''.join(ret)
return traverse(nodes)

Categories