Earlier this week I asked a generic question in a related SO community regarding constructing mathematical trees using OOP. The main takeaway was that the Composite and Interpreter patterns were the go-to patterns for this kind of application.
I then spent several days looking around online for resources on how these are constructed. I'm still convinced that I do not need to construct an entire interpreter and that a composite might be sufficient for my purposes.
From the other question I was trying to construct this tree:
Without using OOP, I'd probably do something like this:
import numpy as np
def root(B, A):
return B+A
def A(x,y,z):
return x*np.log(y)+y**z
def B(alpha, y):
return alpha*y
def alpha(x,y,w):
return x*y+w
if __name__=='__main__':
x,y,z,w = 1,2,3,4
result = root(B(alpha(x,y,w),y), A(x,y,z))
This would give a correct result of 20.693147180559947. I tried to use the composite pattern to do something similar:
class ChildElement:
'''Class representing objects at the bottom of the hierarchy tree.'''
def __init__(self, value):
self.value = value
def __repr__(self):
return "class ChildElement with value"+str(self.value)
def component_method(self):
return self.value
class CompositeElement:
'''Class representing objects at any level of the hierarchy tree except for the bottom level.
Maintains the child objects by adding and removing them from the tree structure.'''
def __init__(self, func):
self.func = func
self.children = []
def __repr__(self):
return "class Composite element"
def append_child(self, child):
'''Adds the supplied child element to the list of children elements "children".'''
self.children.append(child)
def remove_child(self, child):
'''Removes the supplied child element from the list of children elements "children".'''
self.children.remove(child)
def component_method(self):
'''WHAT TO INCLUDE HERE?'''
if __name__=='__main__':
import numpy as np
def e_func(A, B):
return A+B
def A_func(x,y,z):
return x*np.log(y)+y**z
def B_func(alpha,y):
return alpha*y
def alpha_func(x,y,w):
return x*y+w
x = ChildElement(1)
y = ChildElement(2)
z = ChildElement(3)
w = ChildElement(4)
e = CompositeElement(e_func)
A = CompositeElement(A_func)
B = CompositeElement(B_func)
alpha = CompositeElement(alpha_func)
e.children = [A, B]
A.children = [x, y, z]
B.children = [alpha, y]
alpha.children = [x, y, w]
e.component_method()
I got stuck in the last line, however. It seems that if I call the component_method at the level of composite class instance e, it will not work, since the architecture is not built to handle adding two Child or Composite objects.
How can I get this to work? What should the component_method for my CompositeElement class contain?
def component_method(self):
values = [child.component_method() for child in self.children]
return self.func(*values)
This will evaluate the child nodes and pass the values to the function of the node itself, returning the value.
Related
My __repr__ method works fine using objects created in it's class, but with objects that were created with the help of importing a library and using methods from it, it only represented the memory address...
from roster import student_roster #I only got the list if students from here
import itertools as it
class ClassroomOrganizer:
def __init__(self):
self.sorted_names = self._sort_alphabetically(student_roster)
def __repr__(self):
return f'{self.get_combinations(2)}'
def __iter__(self):
self.c = 0
return self
def __next__(self):
if self.c < len(self.sorted_names):
x = self.sorted_names[self.c]
self.c += 1
return x
else:
raise StopIteration
def _sort_alphabetically(self,students):
names = []
for student_info in students:
name = student_info['name']
names.append(name)
return sorted(`your text`names)
def get_students_with_subject(self, subject):
selected_students = []
for student in student_roster:
if student['favorite_subject'] == subject:
selected_students.append((student['name'], subject))
return selected_students
def get_combinations(self, r):
return it.combinations(self.sorted_names, r)
a = ClassroomOrganizer()
# for i in a:
# print(i)
print(repr(a))
I tried displaying objects that don't rely on anther library, and they dispayed properly.
The issue I was facing was linked to me not understanding the nature of the object. itertools.combinations is an iterable, and in order to represent the values stored I needed to either:
unpack it inside a variable like:
def get_combinations(self, r):
*res, = it.combinations(self.sorted_names, r)
return res
Iter through it inside a loop and leave the original code intact like
for i in a.get_combinations(2):
print(i)
I prefer the second solution
I have a class built in a way as follows for building a tree structure.
class Node(object):
def __init__(self, data):
self.data = data
self.children = []
def add_child(self, obj):
self.children.append(obj)
n00 = Node('n00')
n00.add_child(Node('n000'))
n00.add_child(Node('n001'))
n0 = Node('n0')
n0.add_child(n00)
n0.add_child(Node('n01'))
n0.add_child(Node('n02'))
n1 = Node('n1')
n1.add_child(Node('n10'))
n1.add_child(Node('n11'))
n20 = Node('n20')
n20.add_child(Node('n200'))
n20.add_child(Node('n201'))
n2 = Node('n2')
n2.add_child(n20)
n2.add_child(Node('n21'))
h = Node('head')
h.add_child(n00)
h.add_child(n01)
h.add_child(n02)
Now that when I want to only access an item, it can be done by with a simple function such as:
def access(tree, *id):
item = tree
for i in id:
item = item.children[i]
return item.data
print(access(h,0,1))
The problem is when I want to make changes to any node, I cannot use this method and always need to manually type in the lengthy members such as:
h.children[1].children[0].data = 'new value'
or
h.children[0].children[0].children[1].add_child(Node('n0010'))
Now whenever the depth of the tree gets deeper, it becomes quite painful to repeatedly type all this.
Is there a 'Python' way to make changes to items in a tree similar to the access method?
Just go ahead and modify the node: Use the same "walking" technique as in your access method, only then don't return item.data but assign a new value to it:
def modify(new_data, tree, *id):
item = tree
for i in id:
item = item.children[i]
item.data = new_data
Example:
print(access(h, 0, 1))
modify("n001new", h, 0, 1)
print(access(h, 0, 1))
Which prints:
n001
n001new
Same thing for adding children:
def insert_child(new_child, tree, *id):
item = tree
for i in id:
item = item.children[i]
item.add_child(new_child)
Call it like:
insert_child(Node('n0010'), h, 0, 1)
If you know the "path" in the tree of the node you wish to edit, you could create a method to your Node class to return that node using recursion. Something like this:
def getnode(self, path):
if len(path) > 1:
return self.children[path[0]].getnode(path[1:])
else:
return self.children[path[0]]
Here path is a tuple or a list. For example h.getnode((1, 0)).data is equivalent to h.children[1].children[0].data
You can improve this simple method with some try except block to prevent errors in case a tuple is too long or with wrong index, if needed.
EDIT
using the * operator maybe is simple:
def getnode(self, *path):
if len(path) > 1:
return self.children[path[0]].getnode(*path[1:])
else:
return self.children[path[0]]
this way you just write h.getnode(1, 0).data (no double parentheses)
I know my question is somewhat confusing, but I'll try my best to make myself clear. Here's a sample code I've made to try to illustrate the problem:
class X:
def __init__(self, p=None):
self.parent = p
class Y(X):
def __init__(self, p=None):
X.__init__(self, p)
def recursive_check(self):
if (self.parent.parent.parent):
print(self.parent.parent.parent)
x0 = X()
x1 = X(x0)
x2 = X(x1)
y = Y(x2)
y.recursive_check()
As you can see, if you try to access self.parent.parent.parent.parent you'll get a None. The problem is that I don't know how nested the parents can be, so I can't directly test if they exist like that. I want to check recursively the depth of the nest, it could have 100 instances nesting on each other. For the sake of this example, I just need to print each parent address if it exists.
EDIT:
Considering the accepted answer, that's how I've achieved what I wanted:
class X:
def __init__(self, p=None):
self.parent = p
def recursive_check(self):
if self.parent:
print(self.parent)
self.parent.recursive_check()
class Y(X):
def __init__(self, p=None):
X.__init__(self, p)
x0 = X()
x1 = X(x0)
x2 = X(x1)
y = Y(x2)
y.recursive_check()
OBS: I'm developing a GUI for Pygame and this has to do with nested Panels (Layout Management). I don't know how nested the panels can be.
The following will do the memory address printing:
def recursive_check(self):
if self.parent:
print(id(self.parent))
self.parent.recursive_check()
If your ancestry can have circles, you'd have to collect each parent and pass that set on down the recursive calls to avoid infinite recursion.
Just use a loop, which avoids runtime errors when the stack of parents gets too large.
def recursive_check(self):
top = self
p = self.parent
while p is not None:
top = p
p = top.parent
print(top)
This recursive function (search_Bases) would hopefully iterate through each base class and __init__ it. How do I refer to each class's self, without actually using self? I've tried a couple things but I can't figure it out. When I change the Child() class up to do something similar, it works. So I have no clue what to do next.
def search_Bases(child=0):
if child.__bases__:
for parent in child.__bases__:
parent.__init__(self) # <-----I can't figure out how to initiate the class
# without referring to 'self'....
search_Bases(parent)
class Female_Grandparent:
def __init__(self):
self.grandma_name = 'Grandma'
class Male_Grandparent:
def __init__(self):
self.grandpa_name = 'Grandpa'
class Female_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
Female_Grandparent.__init__(self)
Male_Grandparent.__init__(self)
self.female_parent_name = 'Mother'
class Male_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
Female_Grandparent.__init__(self)
Male_Grandparent.__init__(self)
self.male_parent_name = 'Father'
class Child(Female_Parent, Male_Parent):
def __init__(self):
Female_Parent.__init__(self)
Male_Parent.__init__(self)
#search_Bases(Child)
child = Child()
print child.grandma_name
I don't think you properly understand class inheritance. In Python,
class Female_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
means that Female_Parent IS-A Male_Grandparent, which seems unlikely. What you meant to say was
class Female_Parent(object):
def __init__(self, female_grandparent, male_grandparent):
This also has problems, in that the role changes depending on who is asking - by definition, a Male_Grandparent (of his grandchildren) is also a Male_Parent (of his children) who is also a Child (of his parents).
You can boil all your classes down to
class Person(object):
def __init__(self, mother, father):
and derive further relationships from there. This gives a much simpler structure, without the point-of-view contradictions, but still results in problems evaluating further relationships because a given person's links only go "up" - a given person knows who their parents were, but can't identify their children.
You could keep a list of all your Persons and search the list each time (like a mother going around the kindergarten saying, "Are you my child? You? Are YOU my child?") but this seems very inefficient.
Instead, you can make each relationship two-way - each parent has a list of all their children and each child has a list of all their parents. It makes adding and removing people a little harder, but is well worth it.
The following is longer than I like but as short as I could make it; it should suit your needs much better!
class Person(object):
def __init__(self, name, sex, parents=None, children=None):
"""
Create a Person
"""
self.name = name
self.sex = sex # 'M' or 'F'
self.parents = set()
if parents is not None:
for p in parents:
self.add_parent(p)
self.children = set()
if children is not None:
for c in children:
self.add_child(c)
def add_parent(self, p):
self.parents.add(p)
p.children.add(self)
def add_child(self, c):
self.children.add(c)
c.parents.add(self)
def __str__(self):
return self.name
def __repr__(self):
return "Person('{}', '{}')".format(self.name, self.sex)
#
# Immediate relationships
#
# Each fn returns a set of people who fulfill the stated relationship
#
def _parent(self):
return self.parents
def _sibling(self):
return set().union(*(p.children for p in self.parents)) - set([self])
def _child(self):
return self.children
def _female(self):
if self.sex=='F':
return set([self])
else:
return set()
def _male(self):
if self.sex=='M':
return set([self])
else:
return set()
def relation(self, *rels):
"""
Find the set of all people who fulfill the stated relationship
Ex:
self.relation("parent", "siblings") # returns all aunts and uncles of self
"""
# start with the current person
ps = set([self])
for rel in rels:
# each argument is either a function or a string
if callable(rel):
# run the function against all people in the current set
# and collect the results to a new set
ps = set().union(*(rel(p) for p in ps))
else:
# recurse to evaluate the string
do = Person._relations[rel]
ps = set().union(*(p.relation(*do) for p in ps))
return ps
def print_relation(self, *rels):
print ', '.join(str(p) for p in self.relation(*rels))
#
# Extended relationships
#
# Supplies the necessary information for Person.relation() to do its job -
# Each key refers to a recursive function tree (nodes are string values, leaves are functions)
#
# (Unfortunately this table cannot be created until the Person class is finalized)
#
Person._relations = {
"parent": (Person._parent,),
"mother": (Person._parent, Person._female),
"father": (Person._parent, Person._male),
"sibling": (Person._sibling,),
"sister": (Person._sibling, Person._female),
"brother": (Person._sibling, Person._male),
"child": (Person._child,),
"daughter": (Person._child, Person._female),
"son": (Person._child, Person._male),
"grandparent": ("parent", "parent"),
"grandmother": ("parent", "mother"),
"grandfather": ("parent", "father"),
"aunt": ("parent", "sister"),
"uncle": ("parent", "brother"),
"cousin": ("parent", "sibling", "child"),
"niece": ("sibling", "daughter"),
"nephew": ("sibling", "son"),
"grandchild": ("child", "child"),
"grandson": ("child", "son"),
"granddaughter": ("child", "daughter")
}
and now, in action:
mm = Person('Grandma', 'F')
mf = Person('Grandpa', 'M')
m = Person('Mom', 'F', [mm, mf])
fm = Person('Nana', 'F')
ff = Person('Papi', 'M')
f = Person('Dad', 'M', [fm, ff])
me = Person('Me', 'M', [m, f])
s = Person('Sis', 'F', [m, f])
joe = Person('Brother-in-law', 'M')
s1 = Person('Andy', 'M', [s, joe])
s2 = Person('Betty', 'F', [s, joe])
s3 = Person('Carl', 'M', [s, joe])
me.print_relation("grandmother") # returns 'Nana, Grandma'
me.print_relation("nephew") # returns 'Andy, Carl'
Basically I want to be able to have each node of type tree have a Data field and a list of branches. This list should contain a number of objects of type Tree.
I think I have the actual implementation of the list down, but I get strange behavior when I try using the getLeaves method. Basically it calls itself recursively and never returns, and the way that happens is somehow the second node of the tree gets it's first branch set as itself (I think).
class Tree:
"""Basic tree graph datatype"""
branches = []
def __init__(self, root):
self.root = root
def addBranch (self, addition):
"""Adds another object of type Tree as a branch"""
self.branches += [addition]
def getLeaves (self):
"""returns the leaves of a given branch. For leaves of the tree, specify root"""
print (len(self.branches))
if (len(self.branches) == 0):
return self.root
else:
branchSum = []
for b in self.branches:
branchSum += b.getLeaves()
return (branchSum)
Your 'branches' variable is a class member, not an instance member. You need to initialize the 'branches' instance variable in the constructor:
class Tree:
"""Basic tree graph datatype"""
def __init__(self, root):
self.branches = []
self.root = root
The rest of your code looks good.
Is self.root the parent of said tree? In that case, getLeaves() should return self if it has no branches (len(self.branches)==0) instead of self.root as you have it there. Also, if you do have child branches you should include self within branchSum.
Possible solution (your source code with small changes):
class Tree:
def __init__(self, data):
"""Basic tree graph datatype"""
self.data = data
self.branches = []
def addBranch (self, addition):
"""Adds another object of type Tree as a branch"""
self.branches.append(addition)
def getLeaves (self):
"""returns the leaves of a given branch. For
leaves of the tree, specify data"""
if len(self.branches) == 0:
return self.data
else:
branchSum = []
for b in self.branches:
branchSum.append(b.getLeaves())
return branchSum
## Use it
t0 = Tree("t0")
t1 = Tree("t1")
t2 = Tree("t2")
t3 = Tree("t3")
t4 = Tree("t4")
t0.addBranch(t1)
t0.addBranch(t4)
t1.addBranch(t2)
t1.addBranch(t3)
print(t0.getLeaves())
Output:
[['t2', 't3'], 't4']
Remarks:
Looks that some formatting is broken in your code.
Not really sure if this is what you want. Do you want all the leaves in one level of the list? (If so the source code has to be adapted.)