From 6cec940726547a54d11009305d1d182df3676919 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 14:08:04 -0400 Subject: [PATCH 1/9] First stab at creating Node and Tree base classes, to enable traversing of them. Untested. --- __init__.py | 0 nodefactory.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 __init__.py create mode 100644 nodefactory.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nodefactory.py b/nodefactory.py new file mode 100644 index 0000000..828a4c6 --- /dev/null +++ b/nodefactory.py @@ -0,0 +1,74 @@ +class ParentDoesNotExist(Exception): + pass + + +class SiblingDoesNotExist(Exception): + pass + + +class Node(list): + """ + A Node is a nothing more than a list that tracks a little extra information about where it exists + in relation to other nodes + """ + position = 0 + depth = 0 + def __init__(self, leaves=[], parent=None, *args, **kwargs): + self.leaves = leaves + self.parent = parent + super(list, self).__init__(*args, **kwargs) + + def add_leaf(self, node): + node.parent = self + node.position = len(self.leaves) + node.depth = self.depth + 1 + self.leaves.append(node) + + +class Tree(object): + """ + Acts as a 'container' for Node objects, to be organized in a tree structure with a single base node. + """ + def __init__(self, root): + if type(root) != Node: + raise TypeError('root must be of type Node') + self.root = root + self.reset() + + def reset(self): + self.current = self.root + + def move_to(self, node): + """Wrapper for moving current pointer""" + self.current = node + + def move_up(self): + """Move to the parent node if it exists""" + if type(self.current.parent) != Node: + raise ParentDoesNotExist('Parent node does not exist') + self.move_to(self.current.parent) + + def next(self): + """Move to the next sibling of the current node, if it exists""" + proposed_position = self.current.position + 1 + try: + proposed_current = self.current.parent[proposed_position] + except IndexError: + #: if next sibling doesn't exist + self.handle_sibling_error(self.current.depth, proposed_position) + except TypeError: + #: if parent is None + self.handle_sibling_error(self.current.depth, proposed_position) + else: + self.move_to(proposed_current) + return self.current + + def previous(self): + """Move to the previous sibling of the current node, if it exists""" + if self.current.position == 0: + self.handle_sibling_error(self.current.depth, -1) + self.move_to(self.current.parent[self.current.position - 1]) + return self.current + + def handle_sibling_error(self, depth, position): + raise SiblingDoesNotExist('Node does not exist at depth: %d, position: %d' % (depth, position)) From dcea61b6910e43ef1a39dc4ffe7ab1406156ff30 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 14:33:49 -0400 Subject: [PATCH 2/9] Add value field to Node, simplify --- nodefactory.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nodefactory.py b/nodefactory.py index 828a4c6..cd67d7d 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -13,16 +13,18 @@ class Node(list): """ position = 0 depth = 0 - def __init__(self, leaves=[], parent=None, *args, **kwargs): - self.leaves = leaves + def __init__(self, value, children=[], parent=None, *args, **kwargs): + self.value = value + for child in self.children: + self.add_child(child) self.parent = parent super(list, self).__init__(*args, **kwargs) - def add_leaf(self, node): + def add_child(self, node): node.parent = self - node.position = len(self.leaves) + node.position = len(self) node.depth = self.depth + 1 - self.leaves.append(node) + self.append(node) class Tree(object): From ea31ee92067a55189868cb2a0d323714ab942664 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 14:59:41 -0400 Subject: [PATCH 3/9] Added MathNode class --- nodefactory.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/nodefactory.py b/nodefactory.py index cd67d7d..7954949 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -6,6 +6,10 @@ class SiblingDoesNotExist(Exception): pass +class InvalidMathNode(Exception): + pass + + class Node(list): """ A Node is a nothing more than a list that tracks a little extra information about where it exists @@ -74,3 +78,26 @@ def previous(self): def handle_sibling_error(self, depth, position): raise SiblingDoesNotExist('Node does not exist at depth: %d, position: %d' % (depth, position)) + + +class MathNode(Node): + """ + Node with rules limiting what values it may represent + """ + nodetype = 'operator' + def __init__(self, value, *args, **kwargs): + valid_operators = ['-', '+', '/', '*'] + is_valid = False + if value in valid_operators: + is_valid = True + else: + try: + if value.real == value: + self.nodetype = 'number' + is_valid = True + except AttributeError: + is_valid = False + if not is_valid: + raise InvalidMathNode('value must be math expression or real number') + + super(list, self).__init__(value, *args, **kwargs) From 010bbfa25280f7bf4bab7377b4d18cf09cb523c1 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 15:08:46 -0400 Subject: [PATCH 4/9] Allow Tree to work with subclasses of Node also --- nodefactory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nodefactory.py b/nodefactory.py index 7954949..b83c81c 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -36,8 +36,8 @@ class Tree(object): Acts as a 'container' for Node objects, to be organized in a tree structure with a single base node. """ def __init__(self, root): - if type(root) != Node: - raise TypeError('root must be of type Node') + if not isinstance(root, Node): + raise TypeError('root must be of a Node or subclass of Node') self.root = root self.reset() @@ -50,7 +50,7 @@ def move_to(self, node): def move_up(self): """Move to the parent node if it exists""" - if type(self.current.parent) != Node: + if not isinstance(self.current.parent, Node): raise ParentDoesNotExist('Parent node does not exist') self.move_to(self.current.parent) From 0d021b601da90c3439a07f935f8cf5e931f12068 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 23:53:27 -0400 Subject: [PATCH 5/9] Rewrite math_tree_calculator, add shortcut methods to easily import node lists from normal data structures. Added first tests. --- nodefactory.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++--- test.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100755 test.py diff --git a/nodefactory.py b/nodefactory.py index b83c81c..68a1d23 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -19,10 +19,10 @@ class Node(list): depth = 0 def __init__(self, value, children=[], parent=None, *args, **kwargs): self.value = value - for child in self.children: + for child in self: self.add_child(child) self.parent = parent - super(list, self).__init__(*args, **kwargs) + super(Node, self).__init__(*args, **kwargs) def add_child(self, node): node.parent = self @@ -100,4 +100,59 @@ def __init__(self, value, *args, **kwargs): if not is_valid: raise InvalidMathNode('value must be math expression or real number') - super(list, self).__init__(value, *args, **kwargs) + super(MathNode, self).__init__(value, *args, **kwargs) + + +def _get_total(math_mode, left, right): + if left == None: + print 'yes!' + return right + total = left + if math_mode == '-': + total -= right + elif math_mode == '+': + total += right + elif math_mode == '/': + total /= right + elif math_mode == '*': + total *= right + print total + return total + + +def math_tree_calculator(node, total=None, math_mode=None): + print total + if node.nodetype == 'operator': + for child in node: + print child.nodetype + if child.nodetype == 'number': + total = _get_total(node.value, total, child.value) + else: + print total, '-' + total = _get_total(node.value, total, math_tree_calculator(child)) + print total, '--' + else: + total = _get_total(math_mode, total, node.value) + return total + + +def node_pretty_print(node, indent=0): + rjust_indent = indent + if indent: + rjust_indent = indent * 3 + print '{}'.format(node.value).rjust(rjust_indent, ' ') + for child in node: + node_pretty_print(child, indent + 1) + + +def dict_to_math_node(d): + node = MathNode(d['value']) + if 'children' in d: + for child in d['children']: + child_node = dict_to_math_node(child) + node.add_child(child_node) + return node + + +def list_to_math_tree(l): + return dict_to_math_node(l[0]) diff --git a/test.py b/test.py new file mode 100755 index 0000000..2fd59ff --- /dev/null +++ b/test.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +from nodefactory import list_to_math_tree, math_tree_calculator, node_pretty_print + +def unittest(l, expected_result): + node_tree = list_to_math_tree(l) + node_pretty_print(node_tree) + print '---' + total = math_tree_calculator(node_tree) + print 'Total: {}'.format(total) + print 'Expected total: {}'.format(expected_result) + assert total == expected_result + +test_1_list = [ + { + 'value': '*', + 'children': [ + { + 'value': 3, + 'children': [] + }, + { + 'value': 2 + }, + { + 'value': '+', + 'children': [ + { + 'value': 2 + }, + { + 'value': 5 + } + ] + } + ] + } +] + +def test_1(): + unittest(test_1_list, 42) + + +if __name__ == '__main__': + test_1() \ No newline at end of file From e7b8795d2f428efbb2b2ee42063c4b1b213576b2 Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Sun, 5 Apr 2015 23:56:25 -0400 Subject: [PATCH 6/9] Remove debugging output --- nodefactory.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nodefactory.py b/nodefactory.py index 68a1d23..6a57f94 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -105,7 +105,6 @@ def __init__(self, value, *args, **kwargs): def _get_total(math_mode, left, right): if left == None: - print 'yes!' return right total = left if math_mode == '-': @@ -116,21 +115,17 @@ def _get_total(math_mode, left, right): total /= right elif math_mode == '*': total *= right - print total return total def math_tree_calculator(node, total=None, math_mode=None): - print total if node.nodetype == 'operator': for child in node: print child.nodetype if child.nodetype == 'number': total = _get_total(node.value, total, child.value) else: - print total, '-' total = _get_total(node.value, total, math_tree_calculator(child)) - print total, '--' else: total = _get_total(math_mode, total, node.value) return total From 0bd339ef78a8a41d1e3c8ba5c5b829c17e64711b Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Mon, 6 Apr 2015 02:34:02 -0400 Subject: [PATCH 7/9] Removal of unneeded Tree class, extra features on Node class. Reorganization of math_tree_calculator to be more encapsulated. Features added to make testing easier, including random tree generation. Lots of tests. --- nodefactory.py | 151 +++++++++++++++-------------------- test.py | 210 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 93 deletions(-) diff --git a/nodefactory.py b/nodefactory.py index 6a57f94..aad09fb 100644 --- a/nodefactory.py +++ b/nodefactory.py @@ -1,10 +1,3 @@ -class ParentDoesNotExist(Exception): - pass - - -class SiblingDoesNotExist(Exception): - pass - class InvalidMathNode(Exception): pass @@ -12,72 +5,11 @@ class InvalidMathNode(Exception): class Node(list): """ - A Node is a nothing more than a list that tracks a little extra information about where it exists - in relation to other nodes + A Node is a nothing more than a list that tracks values """ - position = 0 - depth = 0 - def __init__(self, value, children=[], parent=None, *args, **kwargs): + def __init__(self, value, *args, **kwargs): self.value = value - for child in self: - self.add_child(child) - self.parent = parent super(Node, self).__init__(*args, **kwargs) - - def add_child(self, node): - node.parent = self - node.position = len(self) - node.depth = self.depth + 1 - self.append(node) - - -class Tree(object): - """ - Acts as a 'container' for Node objects, to be organized in a tree structure with a single base node. - """ - def __init__(self, root): - if not isinstance(root, Node): - raise TypeError('root must be of a Node or subclass of Node') - self.root = root - self.reset() - - def reset(self): - self.current = self.root - - def move_to(self, node): - """Wrapper for moving current pointer""" - self.current = node - - def move_up(self): - """Move to the parent node if it exists""" - if not isinstance(self.current.parent, Node): - raise ParentDoesNotExist('Parent node does not exist') - self.move_to(self.current.parent) - - def next(self): - """Move to the next sibling of the current node, if it exists""" - proposed_position = self.current.position + 1 - try: - proposed_current = self.current.parent[proposed_position] - except IndexError: - #: if next sibling doesn't exist - self.handle_sibling_error(self.current.depth, proposed_position) - except TypeError: - #: if parent is None - self.handle_sibling_error(self.current.depth, proposed_position) - else: - self.move_to(proposed_current) - return self.current - - def previous(self): - """Move to the previous sibling of the current node, if it exists""" - if self.current.position == 0: - self.handle_sibling_error(self.current.depth, -1) - self.move_to(self.current.parent[self.current.position - 1]) - return self.current - - def handle_sibling_error(self, depth, position): - raise SiblingDoesNotExist('Node does not exist at depth: %d, position: %d' % (depth, position)) class MathNode(Node): @@ -103,25 +35,26 @@ def __init__(self, value, *args, **kwargs): super(MathNode, self).__init__(value, *args, **kwargs) -def _get_total(math_mode, left, right): - if left == None: - return right - total = left - if math_mode == '-': - total -= right - elif math_mode == '+': - total += right - elif math_mode == '/': - total /= right - elif math_mode == '*': - total *= right - return total - - def math_tree_calculator(node, total=None, math_mode=None): + """Recursively loop over an entire 'math node tree', calculate its total value""" + def _get_total(math_mode, left, right): + if left == None: + return right#: Happens if there's no total yet + elif right == None: + return left#: Can happen with malformed tree + total = left + if math_mode == '-': + total -= right + elif math_mode == '+': + total += right + elif math_mode == '/': + total /= float(right) + elif math_mode == '*': + total *= right + return total + if node.nodetype == 'operator': for child in node: - print child.nodetype if child.nodetype == 'number': total = _get_total(node.value, total, child.value) else: @@ -132,22 +65,62 @@ def math_tree_calculator(node, total=None, math_mode=None): def node_pretty_print(node, indent=0): + """Prints out nodes for nice display during testing""" + optional_twig = '' + optional_branch = '' rjust_indent = indent if indent: - rjust_indent = indent * 3 - print '{}'.format(node.value).rjust(rjust_indent, ' ') + rjust_indent = indent * 4 + optional_twig = ' {' + if len(node): + optional_branch = '\\' + print '{}{}'.format(optional_twig, node.value).rjust(rjust_indent, ' ') + if optional_branch: + print optional_branch.rjust(rjust_indent + 2) for child in node: node_pretty_print(child, indent + 1) def dict_to_math_node(d): + """Converts dict representations of nodes into actual Node objects""" node = MathNode(d['value']) if 'children' in d: for child in d['children']: child_node = dict_to_math_node(child) - node.add_child(child_node) + node.append(child_node) return node def list_to_math_tree(l): + """Wrapper for dict_to_math_node, allows creation of lists that more resemble our actual node trees""" return dict_to_math_node(l[0]) + + +def create_random_mathnodetree(depth=0): + """Creates random node and calculates, for fun, and testing unexpected situations""" + import random + do_operator = False + if depth == 0: + do_operator = True + else: + base_chance = 60 + adjusted_chance = base_chance - (depth * 2) + randint = random.randint(0, 100) + if randint <= adjusted_chance: + do_operator = True + + if do_operator: + value = random.choice(['-', '+', '/', '*']) + else: + value = random.randint(1, 40) + node = MathNode(value=value) + + if do_operator: + if depth == 0: + #: Make sure we at least go one deep + child_length = random.randint(1, 6) + else: + child_length = random.randint(0, 5) + for i in range(child_length): + node.append(create_random_mathnodetree(depth=depth+1)) + return node diff --git a/test.py b/test.py index 2fd59ff..18f7060 100755 --- a/test.py +++ b/test.py @@ -1,6 +1,7 @@ #!/usr/bin/python -from nodefactory import list_to_math_tree, math_tree_calculator, node_pretty_print +from nodefactory import list_to_math_tree, math_tree_calculator, node_pretty_print, create_random_mathnodetree + def unittest(l, expected_result): node_tree = list_to_math_tree(l) @@ -10,14 +11,15 @@ def unittest(l, expected_result): print 'Total: {}'.format(total) print 'Expected total: {}'.format(expected_result) assert total == expected_result + print '---------' + test_1_list = [ { 'value': '*', 'children': [ { - 'value': 3, - 'children': [] + 'value': 3 }, { 'value': 2 @@ -38,8 +40,208 @@ def unittest(l, expected_result): ] def test_1(): + """Normal, 2 levels deep""" unittest(test_1_list, 42) +test_2_list = [ + { + 'value': '-', + 'children': [] + } +] + + +def test_2(): + """Missing children""" + unittest(test_2_list, None) + + +test_3_list = [ + { + 'value': '+', + 'children': [ + { + 'value': '*' + } + ] + } +] + + +def test_3(): + """Non-numeric value in leaf node""" + unittest(test_3_list, None) + + +test_4_list = [ + { + 'value': '*', + 'children': [ + { + 'value': 3, + }, + { + 'value': 5, + 'children': [ + { + 'value': 6 + }, + { + 'value': 9 + }, + { + 'value': '/', + 'children': [ + { + 'value': 8 + } + ] + } + ] + }, + { + 'value': '+', + 'children': [ + { + 'value': 4 + }, + { + 'value': 2 + } + ] + }, + { + 'value': 2, + }, + ] + } +] + + +def test_4(): + """Children in numeric nodes""" + unittest(test_4_list, 180) + + +test_5_list = [ + { + 'value': '+', + 'children': [ + { + 'value': 2.0 + }, + { + 'value': '*', + 'children': [ + { + 'value': 2 + }, + { + 'value': '+', + 'children': [ + { + 'value': 1 + }, + { + 'value': 2 + } + ] + } + ] + }, + { + 'value': '/', + 'children': [ + { + 'value': 5.0 + }, + { + 'value': 2 + } + ] + }, + { + 'value': '-', + 'children': [ + { + 'value': 10 + }, + { + 'value': 5 + } + ] + } + ] + } +] + + +def test_5(): + """Every operator""" + unittest(test_5_list, 15.5) + + +test_6_list = [ + { + 'value': '+', + 'children': [ + { + 'value': '+', + 'children': [ + { + 'value': '+', + 'children': [ + { + 'value': '+', + 'children': [ + { + 'value': 10 + } + ] + } + ] + } + ] + }, + { + 'value': '+', + 'children': [ + { + 'value': '+', + 'children': [ + { + 'value': 4 + } + ] + } + ] + } + ] + } +] + + +def test_6(): + """Lots of levels""" + unittest(test_6_list, 14) + + +def do_optional_test(): + do_test = raw_input('Generate random node and calculate? (y/n) ') + if do_test == 'y': + random_node = create_random_mathnodetree() + node_pretty_print(random_node) + total = math_tree_calculator(random_node) + print 'Total: {}'.format(total) + + if __name__ == '__main__': - test_1() \ No newline at end of file + test_1() + test_2() + test_3() + test_4() + test_5() + test_6() + do_optional_test() + From 3089c4dc03a658989de47315243e1d9c1ab4efec Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Mon, 6 Apr 2015 02:43:17 -0400 Subject: [PATCH 8/9] Added SQL code, notes --- employeeprojects.sql | 31 +++++++++++++++++++++++++++++++ notes.txt | 11 +++++++++++ 2 files changed, 42 insertions(+) create mode 100644 employeeprojects.sql create mode 100644 notes.txt diff --git a/employeeprojects.sql b/employeeprojects.sql new file mode 100644 index 0000000..96cc83f --- /dev/null +++ b/employeeprojects.sql @@ -0,0 +1,31 @@ +/*Query 1: select project names ordered by project name*/; + +select +project_name +from +projects +order by +project_name; + +/*Query 2: select sum of salaries of employees on each project*/; + +select sum(salary) total_salaries, project_name +from projects p, employee_projects ep, employees e +where ep.project_id=p.project_id +and e.id=ep.employee_id +group by p.project_id +order by project_name; + +/*Query 3: Select average age of employees across projects*/; + +select avg(age) average_age, project_name +from projects p, employee_projects ep, employees e +where ep.project_id=p.project_id +and e.id=ep.employee_id +group by p.project_id +order by project_name; + +/*Sorry about the shorthand joins, and all lowercase, I really am that lazy :) +Also, I didn't see any point in including a count() clause since it will group together fine with only the sum() or avg(). +As far as indexes, pretty much anything with a _id is good practice, and should be an actual foreign key. +Also, since we're sorting on project_name it can't hurt, but it really depends on how many projects there are.*/; \ No newline at end of file diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..4efc6d1 --- /dev/null +++ b/notes.txt @@ -0,0 +1,11 @@ +I tried to put comments in the code wherever possible, but here is a high-level overview: + +1. nodefactory.py - contains all the relevant classes and functions. The most relevant are Node, MathNode, and math_tree_calculator. Also of note are the data generation functions which are used for testing. + +2. tests.py - very simple unit tests using simple assert statements testing as many iterations of things I feel could go wrong as possible, as well as the optional choice at the end to generate a random tree and attempt to calculate it. + +3. employeeprojects.sql - all the SQL statements as I understood them. See comments for my answer about indexes. + +Abel Mohler +abel@wayfarerweb.com +828-713-3003 \ No newline at end of file From 4ae0f5784de4067e9216f987c982f39a7097514d Mon Sep 17 00:00:00 2001 From: Abel Mohler Date: Mon, 6 Apr 2015 02:45:20 -0400 Subject: [PATCH 9/9] Very slight change to test output --- test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.py b/test.py index 18f7060..79858df 100755 --- a/test.py +++ b/test.py @@ -228,7 +228,7 @@ def test_6(): def do_optional_test(): - do_test = raw_input('Generate random node and calculate? (y/n) ') + do_test = raw_input('Generate random tree and calculate? (y/n) ') if do_test == 'y': random_node = create_random_mathnodetree() node_pretty_print(random_node)