Haskell equivalent of data constructors in Python? - python

In Haskell, I can define a binary tree as follows:
data Bint a = Leaf a | Branch a (Bint a) (Bint a)
then I can some operations on it as follows:
height (Leaf a) = 1
height (Branch a l r) = 1 + (max (height l) (height r))
count (Leaf a) = 1
count (Branch a l r) = 1 + (count l) + (count r)
I know Python doesn't has equivalent of data in Haskell. If it has, please tell.
So, how does one define a binary tree in Python and how to implement the above two functions in it?

I am going for a close analogue to Haskell an functional programming here. This is not very "pythonic" in a sense. Especially, it's not object oriented. It's still useful and clean, though.
A datatype is a class. A datatype with multiple data constructors is a class with extra information about how it is constructed. And of course, it needs some data. Use the constructor to assure that it all trees are legal:
class BinTree (object):
def __init__(self, value=None, left=None, right=None):
if left == None and right == None and value != None:
self.isLeaf = True
self.value = value
elif left != None and right != None and value == None:
self.isLeaf = False
self.value = (left, right)
else:
raise ArgumentError("some help message")
This constructor is a bit inconvenient to call, so have some smart constructors that are easy to use:
def leaf(value):
return BinTree(value=value)
def branch(left, right):
return BinTree(left=left, right=right)
How do we get the values out? Let's make some helpers for that, too:
def left(tree):
if tree.isLeaf:
raise ArgumentError ("tree is leaf")
else:
return tree.value[0]
def right(tree):
if tree.isLeaf:
raise ArgumentError ("tree is leaf")
else:
return tree.value[1]
def value(tree):
if not tree.isLeaf:
raise ArgumentError ("tree is branch")
else:
return tree.value
That's it. You got a pure "algebraic" data type which can be accessed with functions:
def count(bin_tree):
if bin_tree.isLeaf:
return 1
else:
return count(left(bin_tree))+count(right(bin_tree))

The closest thing would probably be classes with methods:
class Leaf:
def __init__(self, value):
self.value = value
def height(self):
return 1
def count(self):
return 1
class Branch:
def __init__(self, left, right):
self.left = left
self.right = right
def height(self):
return 1 + max(self.left.height(), self.right.height())
def count(self):
return 1 + self.left.count() + self.right.count()
While this is somewhat idiomatic and has its own share of upsides, it also lacks some qualities of the Haskell version. Most importantly, the function definitions have to be defined along with the class, in the same module. You can use single-dispatch generic functions instead of methods to get that back. The result is more open-world-y than both methods and Haskell functions, and allows spreading the definition across multiple modules when beneficial.
#singledispatch
def height(_):
raise NotImplementedError()
#singledispatch
def count(_):
raise NotImplementedError()
class Leaf:
def __init__(self, value):
self.value = value
#height.register(Leaf)
def height_leaf(leaf):
return 1
#height.register(Leaf)
def count_leaf(leaf):
return 1
class Branch:
def __init__(self, left, right):
self.left = left
self.right = right
#height.register(Branch)
def height_branch(b):
return 1 + max(b.left.height(), b.right.height())
#count.register(Branch)
def count_branch(b):
return 1 + b.left.count() + b.right.count()

Python does not have the concept of "data constructor" as Haskell have. You can create classes, like most other OOP languages. Alternatively you can always represent your data type with built-ins and only define the functions to handle them(this is the approach used to implement heaps in the heapq built-in module).
The differences between python and haskell are huge, so it's better to avoid making tight comparisons between syntax/features of haskell and python, otherwise you'll end up writing non-pythonic and inefficient python code.
Even if python does have some functional features, it is not a functional language, so you have to completely change the paradigm of your programs to obtain readable, pythonic and efficient programs.
A possible implementation using classes could be:
class Bint(object):
def __init__(self, value, left=None, right=None):
self.a = value
self.left = left
self.right = right
def height(self):
left_height = self.left.height() if self.left else 0
right_height = self.right.height() if self.right else 0
return 1 + max(left_height, right_height)
def count(self):
left_count = self.left.count() if self.left else 0
right_height = self.right.count() if self.right else 0
return 1 + left_count + right_count
The code may be simplified a bit providing a "smarter" default value for left and right:
class Nil(object):
def height(self):
return 0
count = height
nil = Nil()
class Bint(object):
def __init__(self, value, left=nil, right=nil):
self.value = value
self.left = left
self.right = right
def height(self):
return 1 + max(self.left.height(), self.right.height())
def count(self):
return 1 + self.left.count() + self.right.count()
Note that these implementations allow nodes with only one children.
However you do not have to use classes to define a data type.
For example you can say that a Bint can be a list of a single element, the value of the root, or a list with three elements: the value, the left child and the right child.
In this case you can define the functions as:
def height(bint):
if len(bint) == 1:
return 1
return 1 + max(height(bint[1]), height(bint[2]))
def count(bint):
if len(bint) == 1:
return 1 + count(bint[1]) + count(bint[2])
Yet another approach would be to use namedtuples:
from collections import namedtuple
Leaf = namedtuple('Leaf', 'value')
Branch = namedtuple('Branch', 'value left right')
def height(bint):
if len(bint) == 1: # or isinstance(bint, Leaf)
return 1
return 1 + max(height(bint.left), height(bint.right))
def count(bint):
if len(bint) == 1: # or isinstance(bint, Leaf)
return 1 + count(bint.left) + count(bint.right)

Five years since last update, but here is my answer.
to make data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show) to python...
Without type check (more like python)
class Tree:
def __init__(self):
pass
def height(self):
pass
def count(self):
pass
class Leaf(Tree):
def __init__(self, value):
self.value = value
def __str__(self):
return("Leaf " + str(self.value))
def height(self):
return 1
def count(self):
return 1
class Branch(Tree):
def __init__(self, left, right):
if isinstance(left, Tree) and isinstance(right, Tree):
self.left = left
self.right = right
else:
raise ValueError
def __str__(self):
return("Branch (" + str(self.left) + " " +
str(self.right) + ")")
def height(self):
return 1 + max(self.left.height(), self.right.height())
def count(self):
return 1 + self.left.count() + self.right.count()
#usage : Branch(Leaf(5), Branch(Leaf(3), Leaf(2)))
With type check (more like haskell)
height, count method at Tree class
class Tree:
def __init__(self, tree, type_):
def typecheck(subtree):
if isinstance(subtree, Leaf):
if not isinstance(subtree.value, type_):
print(subtree.value)
raise ValueError
elif isinstance(subtree, Branch):
typecheck(subtree.left)
typecheck(subtree.right)
else:
raise ValueError
typecheck(tree)
self.tree = tree
self.type_ = type_
def __str__(self):
return ("Tree " + self.type_.__name__ + "\n" + str(self.tree))
def height(self):
if isinstance(self, Leaf):
return 1
elif isinstance(self, Branch):
return 1 + max(self.left.height(), self.right.height())
else:
return self.tree.height()
def count(self):
if isinstance(self, Leaf):
return 1
elif isinstance(self, Branch):
return 1 + self.left.count() + self.right.count()
else:
return self.tree.count()
class Leaf(Tree):
def __init__(self, value):
self.value = value
def __str__(self):
return("Leaf " + str(self.value))
class Branch(Tree):
def __init__(self, left, right):
if isinstance(left, Tree) and isinstance(right, Tree):
self.left = left
self.right = right
else:
raise ValueError
def __str__(self):
return("Branch (" + str(self.left) + " " +
str(self.right) + ")")
#usage tree1 = Tree(Branch(Leaf(5), Branch(Leaf(3), Leaf(2))), int)
#usage tree1.height() -> 3
#usage tree1.count() -> 5
height, count method at Leaf and Branch class
class Tree:
def __init__(self, tree, type_):
def typecheck(subtree):
if isinstance(subtree, Leaf):
if not isinstance(subtree.value, type_):
print(subtree.value)
raise ValueError
elif isinstance(subtree, Branch):
typecheck(subtree.left)
typecheck(subtree.right)
else:
raise ValueError
typecheck(tree)
self.tree = tree
self.type_ = type_
def __str__(self):
return ("Tree " + self.type_.__name__ + "\n" + str(self.tree))
def height(self):
return self.tree.height()
def count(self):
return self.tree.count()
class Leaf(Tree):
def __init__(self, value):
self.value = value
def __str__(self):
return("Leaf " + str(self.value))
def height(self):
return 1
def count(self):
return 1
class Branch(Tree):
def __init__(self, left, right):
if isinstance(left, Tree) and isinstance(right, Tree):
self.left = left
self.right = right
else:
raise ValueError
def __str__(self):
return("Branch (" + str(self.left) + " " +
str(self.right) + ")")
def height(self):
return 1 + max(self.left.height(), self.right.height())
def count(self):
return 1 + self.left.count() + self.right.count()
#usage Tree(Branch(Leaf(5), Branch(Leaf(3), Leaf(2))), int)
#usage tree1.height() -> 3
#usage tree1.count() -> 5
height, count method outside classes (most like haskell)
class Tree:
def __init__(self, tree, type_):
def typecheck(subtree):
if isinstance(subtree, Leaf):
if not isinstance(subtree.value, type_):
print(subtree.value)
raise ValueError
elif isinstance(subtree, Branch):
typecheck(subtree.left)
typecheck(subtree.right)
else:
raise ValueError
typecheck(tree)
self.tree = tree
self.type_ = type_
def __str__(self):
return ("Tree " + self.type_.__name__ + "\n" + str(self.tree))
class Leaf(Tree):
def __init__(self, value):
self.value = value
def __str__(self):
return("Leaf " + str(self.value))
class Branch(Tree):
def __init__(self, left, right):
if isinstance(left, Tree) and isinstance(right, Tree):
self.left = left
self.right = right
else:
raise ValueError
def __str__(self):
return("Branch (" + str(self.left) + " " +
str(self.right) + ")")
def height(tree):
if not isinstance(tree, Tree):
raise ValueError
if isinstance(tree, Leaf):
return 1
elif isinstance(tree, Branch):
return 1 + max(height(tree.left), height(tree.right))
else:
return height(tree.tree)
def count(tree):
if not isinstance(tree, Tree):
raise ValueError
if isinstance(tree, Leaf):
return 1
elif isinstance(tree, Branch):
return 1 + count(tree.left) + count(tree.right)
else:
return count(tree.tree)
#usage tree1 = Tree(Branch(Leaf(5), Branch(Leaf(3), Leaf(2))), int)
#usage height(tree1) -> 3
#usage count(tree1) -> 5

Related

CS50's Introduction to Artificial Intelligence with Python - Knowledge

I'm studyng Harvard's Introduction to Artificial Intelligence with Python course. I'm enjoying a lot. However I downloaded logic file to use Boolean algebra and Knowledge, that simple operations (OR,AND,NOT...) Before I show my doubt I will share the Knowledge class from harvard source code, I hope there isn't issues on it:
link to this class:
Harvard class
logic.py
import itertools
class Sentence():
def evaluate(self, model):
"""Evaluates the logical sentence."""
raise Exception("nothing to evaluate")
def formula(self):
"""Returns string formula representing logical sentence."""
return ""
def symbols(self):
"""Returns a set of all symbols in the logical sentence."""
return set()
#classmethod
def validate(cls, sentence):
if not isinstance(sentence, Sentence):
raise TypeError("must be a logical sentence")
#classmethod
def parenthesize(cls, s):
"""Parenthesizes an expression if not already parenthesized."""
def balanced(s):
"""Checks if a string has balanced parentheses."""
count = 0
for c in s:
if c == "(":
count += 1
elif c == ")":
if count <= 0:
return False
count -= 1
return count == 0
if not len(s) or s.isalpha() or (
s[0] == "(" and s[-1] == ")" and balanced(s[1:-1])
):
return s
else:
return f"({s})"
class Symbol(Sentence):
def __init__(self, name):
self.name = name
def __eq__(self, other):
return isinstance(other, Symbol) and self.name == other.name
def __hash__(self):
return hash(("symbol", self.name))
def __repr__(self):
return self.name
def evaluate(self, model):
try:
return bool(model[self.name])
except KeyError:
raise Exception(f"variable {self.name} not in model")
def formula(self):
return self.name
def symbols(self):
return {self.name}
class Not(Sentence):
def __init__(self, operand):
Sentence.validate(operand)
self.operand = operand
def __eq__(self, other):
return isinstance(other, Not) and self.operand == other.operand
def __hash__(self):
return hash(("not", hash(self.operand)))
def __repr__(self):
return f"Not({self.operand})"
def evaluate(self, model):
return not self.operand.evaluate(model)
def formula(self):
return "¬" + Sentence.parenthesize(self.operand.formula())
def symbols(self):
return self.operand.symbols()
class And(Sentence):
def __init__(self, *conjuncts):
for conjunct in conjuncts:
Sentence.validate(conjunct)
self.conjuncts = list(conjuncts)
def __eq__(self, other):
return isinstance(other, And) and self.conjuncts == other.conjuncts
def __hash__(self):
return hash(
("and", tuple(hash(conjunct) for conjunct in self.conjuncts))
)
def __repr__(self):
conjunctions = ", ".join(
[str(conjunct) for conjunct in self.conjuncts]
)
return f"And({conjunctions})"
def add(self, conjunct):
Sentence.validate(conjunct)
self.conjuncts.append(conjunct)
def evaluate(self, model):
return all(conjunct.evaluate(model) for conjunct in self.conjuncts)
def formula(self):
if len(self.conjuncts) == 1:
return self.conjuncts[0].formula()
return " ∧ ".join([Sentence.parenthesize(conjunct.formula())
for conjunct in self.conjuncts])
def symbols(self):
return set.union(*[conjunct.symbols() for conjunct in self.conjuncts])
class Or(Sentence):
def __init__(self, *disjuncts):
for disjunct in disjuncts:
Sentence.validate(disjunct)
self.disjuncts = list(disjuncts)
def __eq__(self, other):
return isinstance(other, Or) and self.disjuncts == other.disjuncts
def __hash__(self):
return hash(
("or", tuple(hash(disjunct) for disjunct in self.disjuncts))
)
def __repr__(self):
disjuncts = ", ".join([str(disjunct) for disjunct in self.disjuncts])
return f"Or({disjuncts})"
def evaluate(self, model):
return any(disjunct.evaluate(model) for disjunct in self.disjuncts)
def formula(self):
if len(self.disjuncts) == 1:
return self.disjuncts[0].formula()
return " ∨ ".join([Sentence.parenthesize(disjunct.formula())
for disjunct in self.disjuncts])
def symbols(self):
return set.union(*[disjunct.symbols() for disjunct in self.disjuncts])
class Implication(Sentence):
def __init__(self, antecedent, consequent):
Sentence.validate(antecedent)
Sentence.validate(consequent)
self.antecedent = antecedent
self.consequent = consequent
def __eq__(self, other):
return (isinstance(other, Implication)
and self.antecedent == other.antecedent
and self.consequent == other.consequent)
def __hash__(self):
return hash(("implies", hash(self.antecedent), hash(self.consequent)))
def __repr__(self):
return f"Implication({self.antecedent}, {self.consequent})"
def evaluate(self, model):
return ((not self.antecedent.evaluate(model))
or self.consequent.evaluate(model))
def formula(self):
antecedent = Sentence.parenthesize(self.antecedent.formula())
consequent = Sentence.parenthesize(self.consequent.formula())
return f"{antecedent} => {consequent}"
def symbols(self):
return set.union(self.antecedent.symbols(), self.consequent.symbols())
class Biconditional(Sentence):
def __init__(self, left, right):
Sentence.validate(left)
Sentence.validate(right)
self.left = left
self.right = right
def __eq__(self, other):
return (isinstance(other, Biconditional)
and self.left == other.left
and self.right == other.right)
def __hash__(self):
return hash(("biconditional", hash(self.left), hash(self.right)))
def __repr__(self):
return f"Biconditional({self.left}, {self.right})"
def evaluate(self, model):
return ((self.left.evaluate(model)
and self.right.evaluate(model))
or (not self.left.evaluate(model)
and not self.right.evaluate(model)))
def formula(self):
left = Sentence.parenthesize(str(self.left))
right = Sentence.parenthesize(str(self.right))
return f"{left} <=> {right}"
def symbols(self):
return set.union(self.left.symbols(), self.right.symbols())
def model_check(knowledge, query):
"""Checks if knowledge base entails query."""
def check_all(knowledge, query, symbols, model):
"""Checks if knowledge base entails query, given a particular model."""
# If model has an assignment for each symbol
if not symbols:
# If knowledge base is true in model, then query must also be true
if knowledge.evaluate(model):
return query.evaluate(model)
return True
else:
# Choose one of the remaining unused symbols
remaining = symbols.copy()
p = remaining.pop()
# Create a model where the symbol is true
model_true = model.copy()
model_true[p] = True
# Create a model where the symbol is false
model_false = model.copy()
model_false[p] = False
# Ensure entailment holds in both models
return (check_all(knowledge, query, remaining, model_true) and
check_all(knowledge, query, remaining, model_false))
# Get all symbols in both knowledge and query
symbols = set.union(knowledge.symbols(), query.symbols())
# Check that knowledge entails query
return check_all(knowledge, query, symbols, dict())
I know it's too much code, but my doubt is very simple, I tested basic Knowledge Boolean algebra operations such as NOT, AND, and OR. The problem is only at OR fucntion, it always should return TRUE if at least one is true. But it's returning false.
from logic import *
a = Symbol("a")
b = Symbol("b")
# OR
# Error here
orSentence = Or(a, b)
valueOrSentence = model_check(orSentence, a)
print(orSentence.formula() + f" ({valueOrSentence})")
valueOrSentence = model_check(orSentence, Not(a))
print(orSentence.formula() + f" ({valueOrSentence})")
print('---/---/---/')
It should return "true" when check the model, but instead of it it's returning "false"
I prefer to belive there is no error on Harvard logic.py file, what should I do to fix this "OR" logic?
For one specific case of model your knowledge entails but query doesn't, hence it is returning False. There is nothing wrong with it.
When model = {'a': False, 'b': True} then orSentence.evaluate(model) would return True but a.evaluate(model) would return False making the overall result of model_check as False.
If you use andSentence = And(a, b) and then run model_check(andSentence, a), it would return True because for every value of model either andSentence (knowledge) and a (query) both are True or both are False.

Is there any method like divide by or multiply by in python range()?

for java, we can do:
for(int i=100; i>2 ; i=i/2){things to execute}
but what if in python?
is there anything like
for i in range(100:2:something)
could solve this problem?
If you need something simple which you can have at hand at several places, you can create a generator function:
def range_divide(start, end, denominator): # TODO: Think for a better name!
value = start
while value > end:
yield value
value /= denominator
and then do
for value in range_divide(100, 2, 2):
# do_stuff
You could even flexibilize this with
def range_flexible(start, end, action):
value = start
while value > end:
yield value
value = action(value)
and do
for value in range_flexible(100, 2, lambda x: x/2):
# do_stuff
or even
def for_loop(start, cont_condition, action):
value = start
while cont_condition(value):
yield value
value = action(value)
for value in for_loop(100, lambda x: x > 2, lambda x: x/2):
# do_stuff
There isn't by using a range, you could prepopulate a list and iterate over that but you'd be better off using a while loop.
i = 100
while i > 2:
...
i = i / 2
If you want it to look more like a java (or C) for loop, you can define a function that will process the parameters as a string in the C style (at the expense of execution speed):
cachedCFor = dict()
def cFor(params):
if params in cachedCFor: return cachedCFor[params]()
setup,condition,step = [ p.strip() for p in params.split(";") ]
varName = setup.split("=",1)[0].strip()
fn = dict()
code = f"""
def iterator():
{setup}
while {condition}:
yield {varName}
{step}
"""
exec(code,{},fn)
cachedCFor[params] = fn["iterator"]
return fn["iterator"]()
for i in cFor("i=100;i>2;i=i/2"):
print(i)
100
50.0
25.0
12.5
6.25
3.125
Note that the i variable in the string parameter is internal to the iterator and is not accessible within the for loop's code. We could have written for i in cFor("x=100;x>2;x=x/2") and still use i within the loop
That being said, I would still suggest that you embrace Python's way of doing things and not try to reproduce other language's syntax (i.e. use a while statement in this particular case)
for example:
x = 100
while x > 2:
i,x = x,x/2 # using x for the next value allows this to be placed
# at the beginning of the loop (rather than at the end)
# and avoids issues with the continue statement
print(i)
# ... your code ...
Or, you could use a bit of math:
# 6 = int(math.log(100,2))
for i in [100/2**i for i in range(6)]:
print(i)
# Strangely enough, this is actually slower than the cFor() folly
Here's another approach to handle special progressions in a cleaner and more generic fashion. It is a class that implements (and hides) internal workings of a loop variable.
class Loop:
def __init__(self,start=0):
self._firstPass = True
self._value = start
#property
def value(self): return self._value
def start(self,initial):
if self._firstPass : self._value = initial
return self
def next(self,nextValue=None):
if nextValue is None : nextValue = self.value + self._increment
if self._firstPass : self._firstPass = False
else : self._value = nextValue
return self
def up(self,by=1):
return self.next(self.value+by)
def down(self,by=1):
return self.next(self.value-by)
def upTo(self,last,by=1):
if self._firstPass: self._firstPass = False
else: self._value += by
return self.value <= last
def downTo(self,last,by=1):
if self._firstPass: self._firstPass = False
else: self._value -= by
return self.value >= last
def loop(self,condition=True):
self._firstPass = False
return condition
def until(self,condition=False):
self._firstPass = False
return not condition
def __getitem__(self,index): return self.value[index]
def __str__(self): return str(self.value)
def __int__(self): return int(self.value)
def __float__(self): return float(self.value)
def __add__(self,other): return self.value + other
def __sub__(self,other): return self.value - other
def __mul__(self,other): return self.value * other
def __matmul__(self,other): return self.value.__matmul__(other)
def __divmod__(self,other): return divmod(self.value,other)
def __pow__(self,other): return self.value ** other
def __truediv__(self,other): return self.value / other
def __floordiv__(self,other): return self.value // other
def __mod__(self,other): return self.value % other
def __lshift__(self,other): return self.value << other
def __rshift__(self,other): return self.value >> other
def __lt__(self,other): return self.value < other
def __le__(self,other): return self.value <= other
def __eq__(self,other): return self.value == other
def __ne__(self,other): return self.value != other
def __gt__(self,other): return self.value > other
def __ge__(self,other): return self.value >= other
def __and__(self,other): return self.value & other
def __or__(self,other): return self.value | other
def __xor__(self,other): return self.value ^ other
def __invert__(self): return -self.value
def __neg__(self): return -self.value
def __pos__(self): return self.value
def __abs__(self): return abs(self.value)
def __radd__(self, other): return other + self.value
def __rsub__(self, other): return other - self.value
def __rmul__(self, other): return other * self.value
def __rmatmul__(self, other): return other.__matmul__(self.value)
def __rtruediv__(self, other): return other / self.value
def __rfloordiv__(self, other): return other // self.value
def __rmod__(self, other): return other % self.value
def __rdivmod__(self, other): return divmod(other,self.value)
def __rpow__(self, other): return other ** self.value
def __rlshift__(self, other): return other << self.value
def __rrshift__(self, other): return other >> self.value
def __rand__(self, other): return other & self.value
def __rxor__(self, other): return other ^ self.value
def __ror__(self, other): return other | self.value
The class is designed to work with the while statement after initializing a loop variable. The loop variable behaves like a normal int (or float, or str, etc.) when used in calculations and conditions. This allows the progression and stop condition to be expressed as you would write them for an ordinary loop variable. The class adds a few method to control the loop process allowing for non-standard increments/decrements:
For example:
i = Loop()
while i.start(100).next(i//2).loop(i>2):
print(i) # 100, 50, 25, 12, 6 ,3
# Note: to use i for assignment or as parameter use +i or i.value
# example1: j = +i
# example2: for j in range(+i)
#
# i.value cannot be modified during the loop
You can also give a start value in the constructor to make the while statement more concise. The class also has an until() function to invert the stop condition:
i = Loop(start=100)
while i.next(i//2).until(i<=2):
print(i) # 100, 50.0, 25.0, 12.5, 6.25, 3.125
Finally there are a couple of helper functions to implement the simpler loops (although a for in would probably be better in most cases):
i = Loop()
while i.start(1).upTo(10):
print(i) # 1,2,...,9,10
i = Loop()
while i.upTo(100,by=5):
print(i) # 0,5,10,15,20,...,95,100
i = Loop(100)
while i.down(by=5).until(i<20):
print(i) # 100,95,90,...,25,20

Recursively Creating a Binary Tree Given a String Expression in Prefix Notation in Python

Representation of LinkedBinaryTree that should be outputted
import LinkedBinaryTree
def create_expression_tree(prefix_exp_str):
def create_expression_tree_helper(prefix_exp, start_pos):
start_pos += 1
op = ['+', '-', '*', '/']
elem = prefix_exp[start_pos]
if elem == ' ':
elem = prefix_exp[start_pos+1]
start_pos += 1
if elem not in op:
return LinkedBinaryTree.LinkedBinaryTree.Node(int(elem))
else:
left = create_expression_tree_helper(prefix_exp, start_pos)
right = create_expression_tree_helper(prefix_exp, start_pos+2)
return LinkedBinaryTree.LinkedBinaryTree.Node(elem, left, right)
tree = LinkedBinaryTree.LinkedBinaryTree(create_expression_tree_helper(prefix_exp_str, -1))
return tree
tree1 = create_expression_tree('* 2 + - 15 6 4')
for i in tree1.preorder():
print(i.data, end='')
I implemented my own binary tree class which is shown below. Preorder() is a generator for my LinkedBinaryTree that gives the values of the tree in prefix order. With this code, I'm outputting
*2+-151
but it should be outputting
*2+-1564 if the binary expression tree has been created correctly.
I'm aware that there is an issue with numbers greater than 1 digit, but I'm not sure how to fix it without compromising the runtime (ie. using slicing). Also I'm not sure why it is omitting some of the tokens. Any ideas? (The implementation must run in linear time and use no additional parameters in the helper function I've defined).
import ArrayQueue
class LinkedBinaryTree:
class Node:
def __init__(self, data, left=None, right=None):
self.data = data
self.parent = None
self.left = left
if (self.left is not None):
self.left.parent = self
self.right = right
if (self.right is not None):
self.right.parent = self
def __init__(self, root=None):
self.root = root
self.size = self.subtree_count(root)
def __len__(self):
return self.size
def is_empty(self):
return len(self) == 0
def subtree_count(self, root):
if (root is None):
return 0
else:
left_count = self.subtree_count(root.left)
right_count = self.subtree_count(root.right)
return 1 + left_count + right_count
def sum(self):
return self.subtree_sum(self.root)
def subtree_sum(self, root):
if (root is None):
return 0
else:
left_sum = self.subtree_sum(root.left)
right_sum = self.subtree_sum(root.right)
return root.data + left_sum + right_sum
def height(self):
return self.subtree_height(self.root)
def subtree_height(self, root):
if (root.left is None and root.right is None):
return 0
elif (root.left is None):
return 1 + self.subtree_height(root.right)
elif (root.right is None):
return 1 + self.subtree_height(root.left)
else:
left_height = self.subtree_height(root.left)
right_height = self.subtree_height(root.right)
return 1 + max(left_height, right_height)
def preorder(self):
yield from self.subtree_preorder(self.root)
def subtree_preorder(self, root):
if(root is None):
return
else:
yield root
yield from self.subtree_preorder(root.left)
yield from self.subtree_preorder(root.right)
def postorder(self):
yield from self.subtree_postorder(self.root)
def subtree_postorder(self, root):
if(root is None):
return
else:
yield from self.subtree_postorder(root.left)
yield from self.subtree_postorder(root.right)
yield root
def inorder(self):
yield from self.subtree_inorder(self.root)
def subtree_inorder(self, root):
if(root is None):
return
else:
yield from self.subtree_inorder(root.left)
yield root
yield from self.subtree_inorder(root.right)
def breadth_first(self):
if (self.is_empty()):
return
line = ArrayQueue.ArrayQueue()
line.enqueue(self.root)
while (line.is_empty() == False):
curr_node = line.dequeue()
yield curr_node
if (curr_node.left is not None):
line.enqueue(curr_node.left)
if (curr_node.right is not None):
line.enqueue(curr_node.right)
def __iter__(self):
for node in self.breadth_first():
yield node.data
I added the code here for LinkedBinaryTree class. The ArrayQueue class that is used in the implementation of the breadth traversal method is just a basic queue using a Python list as the underlying data structure.
The two issues with your code were:
you processed the characters one by one while several-digit numbers could be present (fixed by splitting the expression to a list)
you did not account for the fact that chained operator expressions could be longer that just your standard increment (fixed by adding a size property to Node
So here is the new Node class
class Node:
def __init__(self, data, left=None, right=None):
self.data = data
self.parent = None
self.left = left
if (self.left is not None):
self.left.parent = self
self.right = right
if (self.right is not None):
self.right.parent = self
#property
def size(self):
l = 1
if self.left is not None:
l += self.left.size
if self.right is not None:
l += self.right.size
return l
and here is the new tree creator
def create_expression_tree(prefix_exp_str):
expr_lst = prefix_exp_str.split(" ")
op = {'+': None, '-': None, '*': None, '/': None}
def create_expression_tree_helper(prefix_exp, start_pos):
start_pos += 1
elem = prefix_exp[start_pos]
node = None
if elem not in op:
node = LinkedBinaryTree.Node(int(elem))
else:
left = create_expression_tree_helper(prefix_exp, start_pos)
incr = left.size
right = create_expression_tree_helper(prefix_exp, start_pos+incr)
node = LinkedBinaryTree.Node(elem, left, right)
return node
tree = LinkedBinaryTree(create_expression_tree_helper(expr_lst, -1))
return tree
EDIT here is a version that does not require to modify the Node class
def create_expression_tree(prefix_exp_str):
expr_lst = prefix_exp_str.split(" ")
op = {'+': None, '-': None, '*': None, '/': None}
def create_expression_tree_helper(prefix_exp, start_pos):
start_pos += 1
elem = prefix_exp[start_pos]
node = None
size = 1
if elem not in op:
node = LinkedBinaryTree.Node(int(elem))
else:
left, left_size = create_expression_tree_helper(prefix_exp, start_pos)
right, right_size = create_expression_tree_helper(prefix_exp, start_pos+left_size)
node = LinkedBinaryTree.Node(elem, left, right)
size += left_size + right_size
return node, size
tree = LinkedBinaryTree(create_expression_tree_helper(expr_lst, -1)[0])
return tree

Adding Student Records to Binary Search Tree in Python

Sorry for all of the code, but for this assignment I am working on, it is necessary due to different references.
This chapter we are working with Binary Trees and Binary Search Trees.
I tested the BinaryTree() and BinarySearchTree() classes with no issue.
My problem is this: we are adding records to a binary search tree as seen in the Student class and the main() function to test the class. According to the assignment:
The Student class has an id and name, getters and setters, a str() function so that you can print a student record, and the operations ==, !=, <=, <, >=, > defined. The comparison operators compare student id's which are intended to be unique. Since we can compare student records, we can now put them into a Binary Search Tree.
My question is, how do I add the records a binary tree if all of the initialized variables are completely different than ones in the BinaryTree() class and the BinarySearchTree() class?
Everytime I run the code, I get errors such as:
AttributeError: 'Student' object has no attribute 'insert'
or
isEmpty() = True
None
isEmpty() = False
None
101(Betty Smith)
101(Betty Smith)
Traceback (most recent call last):
File "/Users/phillipward/Desktop/Lab 10/StudentRecordsTest.py", line 50, in <module>
main()
File "/Users/phillipward/Desktop/Lab 10/StudentRecordsTest.py", line 22, in main
BST.insert(Student(42, "Amy Winchester"))
File "/Users/phillipward/Desktop/Lab 10/BinarySearchTree.py", line 13, in insert
self.getleftChild().insert(data)
File "/Users/phillipward/Desktop/Lab 10/BinarySearchTree.py", line 7, in insert
if(self.isEmpty()):
File "/Users/phillipward/Desktop/Lab 10/binaryTree.py", line 33, in isEmpty
if(self is None or self.data is None):
AttributeError: 'Student' object has no attribute 'data'
I'm stuck on how to tell the program that I the student class is a type of BinarySearchTree.
class BinaryTree():
def __init__(self, data = None, leftChild = None, rightChild = None):
self.data = data
self.leftChild = leftChild
self.rightChild = rightChild
def getData(self):
return self.data
def setData(self, x):
self.data = x
return self.data
def getleftChild(self):
return self.leftChild
def setleftChild(self, x):
self.leftChild = x
return self.leftChild
def getrightChild(self):
return self.rightChild
def setrightChild(self, x):
self.rightChild = x
return self.rightChild
def isEmpty(self):
if(self is None or self.data is None):
return True
else:
return False
def __str__ (self):
return "{0}".format(self.data)
def inOrderTraversal(self):
if (self is None):
return "Empty"
else:
result = ""
if(self.getleftChild() is not None): # When we put payload in, we need equality
result += BinaryTree.inOrderTraversal(self.getleftChild()) + " "
result += str(self.getData())
if (self.getrightChild() is not None):
result += " " + BinaryTree.inOrderTraversal(self.getrightChild()) + " "
return result
def preOrderTraversal(self):
if (self.isEmpty()):
return "Empty"
else:
result = ""
result += str(self.getData())
if (self.getleftChild() is not None):
result += " " + BinaryTree.preOrderTraversal(self.getleftChild()) + " "
if (self.getrightChild() is not None):
result += BinaryTree.preOrderTraversal(self.getrightChild())
return result
def postOrderTraversal(self):
if (self.isEmpty()):
return "Empty"
else:
result = ""
if (self.getleftChild() is not None):
result += BinaryTree.postOrderTraversal(self.getleftChild()) + " "
if (self.getrightChild() is not None):
result += BinaryTree.postOrderTraversal(self.getrightChild()) + " "
result += str(self.getData())
return result
def insert(self, data):
if (self.isEmpty()):
self.setData(data)
elif (data < self.getData()):
if (self.getleftChild() is None):
self.setleftChild(BinarySearchTree(data))
else:
self.getleftChild().insert(data)
else: # data >= self.getData()
if (self.getrightChild() is None):
self.setrightChild(BinarySearchTree(data))
else:
self.getrightChild().insert(data)
def retrieve(self, data):
if self.isEmpty():
return None
elif data == self.getData():
return self.getData()
elif data <= self.getData():
if self.getleftChild() is None:
return None
else:
return self.getleftChild().retrieve(data)
else:
if self.getrightChild() is None:
return None
else:
return self.getrightChild().retrieve(data)
from binaryTree import BinaryTree
class BinarySearchTree(BinaryTree):
def insert(self, data):
if(self.isEmpty()):
self.setData(data)
elif(data < self.getData()):
if(self.getleftChild() is None):
self.setleftChild(data)
else:
self.getleftChild().insert(data)
else: #data >= self.getData()
if(self.getrightChild() is None):
self.setrightChild(data)
else:
self.getrightChild().insert(data)
def retrieve(self, data):
if self.isEmpty():
return None
elif data == self.getData():
return self.getData()
elif data <= self.getData():
if self.getleftChild() is None:
return None
else:
return self.getleftChild().retrieve(data)
else:
if self.getrightChild() is None:
return None
else:
return self.getrightChild().retrieve(data)
def minValue(self):
current = self
while current.leftChild is not None:
current = current.leftChild
return current.data
def maxValue(self):
current = self
while current.rightChild is not None:
current = current.rightChild
return current.data
def isBST(self):
current = self
if current == None:
return True
if current.leftChild.data <= current.data and
current.rightChild.data >= current.data:
return True
else:
return False
class Student():
def __init__(self, id = None, name = ""):
self.__id = id
self.__name = name
def getId(self):
return self.__id
def setId(self, id):
self.__id = id
def getName(self):
return self.__name
def setName(self, name):
self.__id = name
def __str__(self):
if (self is None):
return ""
else:
return str(self.__id) + "(" + self.__name + ")"
def __cmp__(self, other):
if (self is None or other is None):
return 0
else:
return self.__id - other.__id
def __eq__(self, other):
return self.__cmp__(other) == 0
def __ne__(self, other):
return self.__cmp__(other) != 0
def __lt__(self, other):
return self.__cmp__(other) < 0
def __le__(self, other):
return self.__cmp__(other) <= 0
def __gt__(self, other):
return self.__cmp__(other) > 0
def __ge__(self, other):
return self.__cmp__(other) >= 0
from BinarySearchTree import BinarySearchTree
from Student import Student
def main():
BST = BinarySearchTree()
print("isEmpty() = " + str(BST.isEmpty()))
print(BST)
BST.setData(Student(101, "Betty Smith"))
print("isEmpty() = " + str(BST.isEmpty()))
print(BinarySearchTree())
BST.insert(Student(50, "John Adams"))
print(BST)
BST.insert(Student(250, "Mike Jones"))
print(BST)
BST.insert(Student(42, "Amy Winchester"))
print(BST)
BST.insert(Student(31, "Jill Ranger"))
print(BST)
BST.insert(Student(315, "Bob Crachet"))
print(BST)
BST.insert(Student(200, "Karen Wilkins"))
print(BST)
print("\n")
print()
print("Inorder traversal: " + str(BST))
print()
print("Preorder traversal: \n" + BST.preOrderTraversal())
print()
print("Postorder traversal: " + BST.postOrderTraversal())
print()
print("minValue: " + str(BST.minValue()))
print("maxValue: " + str(BST.maxValue()))
print()
print("isBST = " + str(BST.isBST()))
for id in [101, 200, 31, 50, 315, 250, 42]:
print(BST.retrieve(Student(id)))
main()

Python Property, How do I get this to work correctly

I am trying to use OO on python to create a nice structured class. I have an object that all functions from one class will inherit but there are sub functions (getter and setter) that may require one or two additional parameters.
How can I get this type of logic to work correctly.
class XYZ(object):
def __init__(self, cameraId):
self.cameraId = cameraId;
self.index = "";
def get_test(self):
print "Index: " + self.index + " CameraID: " + self.cameraId;
return self.cameraId;
def set_test(self, value, myValue=""):
self.cameraId = value;
self.index = myValue;
return True;
TEST_XYZ = property(get_test,set_test);
You can use tuple-typed values. Note that you don't have to use ; after your statements...
class XYZ(object):
def __init__(self, cameraId):
self.cameraId = cameraId
self.index = ""
def get_test(self):
print "Index: " + self.index + " CameraID: " + self.cameraId
return self.cameraId
def set_test(self, value):
# Require value to be a tuple!
assert(isinstance(value, tuple))
self.cameraId = value[0]
try:
self.index = value[1]
except IndexError:
self.index = ""
return True
TEST_XYZ = property(get_test, set_test)

Categories

Resources