diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 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/nodefactory.py b/nodefactory.py new file mode 100644 index 0000000..aad09fb --- /dev/null +++ b/nodefactory.py @@ -0,0 +1,126 @@ + +class InvalidMathNode(Exception): + pass + + +class Node(list): + """ + A Node is a nothing more than a list that tracks values + """ + def __init__(self, value, *args, **kwargs): + self.value = value + super(Node, self).__init__(*args, **kwargs) + + +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(MathNode, self).__init__(value, *args, **kwargs) + + +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: + if child.nodetype == 'number': + total = _get_total(node.value, total, child.value) + else: + total = _get_total(node.value, total, math_tree_calculator(child)) + else: + total = _get_total(math_mode, total, node.value) + return total + + +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 * 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.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/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 diff --git a/test.py b/test.py new file mode 100755 index 0000000..79858df --- /dev/null +++ b/test.py @@ -0,0 +1,247 @@ +#!/usr/bin/python + +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) + 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 + print '---------' + + +test_1_list = [ + { + 'value': '*', + 'children': [ + { + 'value': 3 + }, + { + 'value': 2 + }, + { + 'value': '+', + 'children': [ + { + 'value': 2 + }, + { + 'value': 5 + } + ] + } + ] + } +] + +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 tree 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() + test_2() + test_3() + test_4() + test_5() + test_6() + do_optional_test() +