CS50's Introduction to Artificial Intelligence with Python - Knowledge - python

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.

Related

String subclass for phonetic Levenshtein distance

I'm trying to create a string subclass, in order to represent Arpabet symbols as single characters. Here is what I got so far:
import Levenshtein
class ArpabetChar(str):
"""
Class that turn string into an Arpabet character.
http://www.speech.cs.cmu.edu/cgi-bin/cmudict
"""
def __init__(self, chars: list):
self._chars = chars
def __repr__(self):
return "".join(char for char in self._chars)
def __str__(self):
return "".join(char for char in self._chars)
def __eq__(self, other):
if self._chars == other._chars:
return True
else:
return False
def __len__(self):
return len(self._chars)
def __getitem__(self, item):
return self._chars[item]
def __iter__(self):
for char in self._chars:
yield ArpabetChar([char])
def __add__(self, other):
added_char = [char for char in self._chars]
for char in other._chars:
added_char.append(char)
return ArpabetChar(added_char)
char1 = ArpabetChar(["AH0"])
char2 = ArpabetChar(["AH1"])
char3 = ArpabetChar(["AE1"])
print("Indexing:", char1[0])
print(f"Length of {char1}: {len(char1)}")
print(f"Length of {char2}: {len(char2)}")
print(f"Levenshtein distance {char1} and {char2}:{Levenshtein.distance(char1, char2)}")
print(f"Levenshtein distance {char1} and {char3}:{Levenshtein.distance(char1, char3)}")
The output that I expect for both calculations is a Levenshtein distance of 1. Any hints or suggestions?

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

Overloading, Referencing, and everything in between

This is a big one and I apologize.
I am practicing to make my coding more modular.
We are tasked to create a Shipyard system.
There are Containers in the Shipyard and Packages in the Containers.
There is a LinkedList program provided that can be imported. I added it on the bottom.
IMPORTANT: I CANNOT ALTER THE SORTED LINKED LIST PROGRAM IN ANY WAY HENCE WHY I USE OVERLOADERS
I have another program which is non-modular. It is specifically made for the assignment at hand and it works for the most part.
Python 3.5
from SortedLList import *
class Shipyard:
def __init__(self):
self._container = SortedLList()
def add(self, owner, destination, weight):
"""
This function does:
1. Adds a container for a specific destination if doesn't exist.
2. Adds a package in a container given that the total weight
doesn't exceed 2000 lbs
3. If it does, it creates another container headed in the same
direction and then the package is inserted there instead.
"""
self._container.insert(Self.Container(destination))
class Container:
def __init__(self, destination):
self._package = SortedLList()
self._dest = destination
self._weight = 0
self._max = 2000
def add_pack(self, owner, destination, weight):
"""
This function adds the weight of the package to the total
container weight. And inserts a Package Singly Linked List
object inside the container.
"""
self._weight += weight
self._package.insert(Self.Package(destination))
def __lt__(self, other):
return self._dest < other._dest
def __ge__(self, other):
return self._dest >= other._dest
def __eq__(self, other):
return self._dest == other._dest
class Package:
def __init__(self, owner, destination, weight):
self._owner = owner
self._dest = destination
self._weight = weight
def __lt__(self, other):
return self._weight < other._weight
def __ge__(self, other):
return self._weight >= other._weight
def __eq__(self, other):
return self._weight == other._weight
class SortedLList :
class _Node :
def __init__(self, elem, next) :
self._elem = elem
self._next = next
def __init__(self) :
self._first = None
self._size = 0
def __len__(self) :
return self._size
def isEmpty(self) :
return len(self) == 0
def first(self):
return self._elem._first
def insert(self, val) :
if (self.isEmpty() or val <self._first._elem):
self._size+=1
self._first = self._Node(val,self._first)
tmpRef=self._first
while(tmpRef._next!=None and val>=tmpRef._next._elem):
tmpRef=tmpRef._next
if val==tmpRef._elem:
return
self._size+=1
tmpRef._next=self._Node(val,tmpRef._next)
return
def isPresent(self, elem) :
tmpRef=self._first
while(tmpRef!=None):
if tmpRef._elem==elem:
return True
tmpRef=tmpRef._next
return False
def delete(self, elem) :
if self.isEmpty() :
return
if elem == self._first._elem :
self._size -= 1
self._first = self._first._next
return
tmpRef = self._first
while (tmpRef._next != None and elem > tmpRef._next._elem) :
tmpRef = tmpRef._next
if tmpRef._next == None : return
if tmpRef._next._elem != elem : return
self._size -= 1
tmpRef._next = tmpRef._next._next
return
def traversePrint(self) :
tmpRef = self._first
while tmpRef != None :
print(tmpRef._elem)
tmpRef = tmpRef._next
class Empty(Exception) :
pass
I want to be able to use the methods in the SortedLList program to be able to display information that I have in the main program.
Is there any way around typing:
print(self._cont._first._elem._dest) or print(self._cont._first._elem._package._first._elem._owner) by using traversePrint() without altering the helper code?
You can inherit SortedLList in a new class and overload the methods you want.
class MySortedLList(SortedLList):
def traversePrint(self):
# Your custom implementation
and then using that instead of SortedLLIst, e.g:
class Shipyard:
def __init__(self):
self._container = MySortedLList()
However,, you asked if you can use the SortedLList.traversePrint to print information about your main program, presumable Shipyard. This probably doesn't make sense because you'd break encapsulation. SortedLList knows about its elements and can only know about Shipyard if you give it a reference to Shipyard. So rather let Shipyard tell you about itself.

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()

Haskell equivalent of data constructors in 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

Categories

Resources