class CallRouter(object): def __init__(self, phone_numbers_unparsed, route_numbers_unparsed): self.phone_numbers_parsed = self.parse_phone_numbers( phone_numbers_unparsed) self.route_numbers_parsed = self.parse_route_numbers( route_numbers_unparsed) # Data Structure: Binary Search Tree of Prefixes (quick search) self.prefixes = BinarySearchTree() # Data Structure: Dictionary of prefixes and best prices (quick price fetching and price updating) self.best_prices = {} # Step 1: Turn import files into useable python objects def parse_phone_numbers(self, phone_numbers_unparsed): pass def parse_route_numbers(self, route_numbers_unparsed): # Take each line in raw data, deconstruct for entry in route_numbers_unparsed: clean_route = entry(',') prefix = clean_route[0] price = clean_route[1] # If not in prefix binary tree, add to binary tree and add to dictionary if self.prefixes.contains(prefix) == False: self.prefixes.insert(prefix) self.best_prices[prefix] = price # If already in prefix binary tree, update compare and update dictionary else: # If stored price is larger than new price if self.best_prices[prefix] > price: self.best_prices[prefix] = price # Step 2: Find the longest matching prefix (using recursion) def routing_cost(self, phone_number): # Find the longest prefix that matches phone number # Note: We are always storing best prices in the parse route numbers method if phone_number is not None: if self.prefixes.contains(phone_number): return self.best_prices[phone_number] else: index_to_slice = len(phone_number) - 1 return self.routing_cost(phone_number[:index_to_slice]) else: return 0 # Step 3: Record best routing costs results into a readable format def record_routing_costs(self, phone_numbers_parsed): results = [] for phone_number in phone_numbers_parsed: cost = self.routing_cost(phone_number) line_item = "{},{}".format(phone_number, cost) results.append(line_item) print(results)
class Set: """Implements a set using BinarySearchTree""" __slots__ = ("data", ) def __init__(self, it=()): self.data = BinarySearchTree(it) def add(self, elm): self.data.insert(elm) def remove(self, elm): self.data.delete(elm) def union(self, other_set): bigger_set = max(self, other_set, key=lambda set: set.size) smaller_set = min(self, other_set, key=lambda set: set.size) return Set(self.items() + other_set.items()) def intersection(self, other_set): bigger_set = max(self, other_set, key=lambda set: set.size) smaller_set = min(self, other_set, key=lambda set: set.size) return Set( tuple(elm for elm in smaller_set.items() if bigger_set.contains(elm))) def difference(self, other_set): res = Set() for elm in self.items(): if not other_set.contains(elm): res.add(elm) return res def is_subset(self, other_set): for elm in self.items(): if not other_set.contains(elm): return False return True def items(self): return self.data.items_in_order() def contains(self, elm): return self.data.contains(elm) @property def size(self): return self.data.size def __eq__(self, other): if isinstance(other, BinarySearchTree): return self.data == other elif isinstance(other, Set): return self.data == other.data else: raise TypeError("Can only compare with BinarySearchTree and Set")
def test_delete_with_3_items(self): # Create a complete binary search tree of 3 items in level-order items = [2, 1, 3] tree = BinarySearchTree(items) assert tree.root.data == 2 assert tree.root.left.data == 1 assert tree.root.right.data == 3 # Test structure of tree after each deletion tree.delete(2) assert tree.size == 2 assert tree.contains(1) is True assert tree.contains(3) is True tree.delete(1) assert tree.size == 1 assert tree.contains(1) is False assert tree.contains(3) is True tree.delete(3) assert tree.size == 0 assert tree.contains(1) is False assert tree.contains(3) is False assert tree.root is None
class TreeSet(AbstractSet): def __init__(self, elements=None): """Initialize this binary tree with the given data.""" self.tree = BinarySearchTree() if elements is not None: for element in elements: self.add(element) @property def size(self): """ Makes size an attribute """ return self.tree.size def __repr__(self): """Return a string representation of this binary tree node.""" return 'Set({!r})'.format(self.tree.items_in_order()) def contains(self, item): """ Checks if item is in the set Time Complexity: O(logN) only grows in relation to the #node vs height starts fast/heavy then peeks with a lot for a long time""" return self.tree.contains(item) def add(self, item): """ Insert one item to the set if it doesn't already exist Time complexity: O(logN) based off contains then just O(1) to add""" if not self.contains(item): self.tree.insert(item) def remove(self, item): """ Check if item exists and remove it or raise Keyerror Time complexity: O() """ if self.contains(item): self.tree.delete(item) else: raise ValueError(item, "Is not in this set") def items(self): return self.tree.items_in_order()
def test_delete_with_3_items(self): # Create a complete binary search tree of 3 items in level-order items = [2, 1, 3] tree = BinarySearchTree(items) assert tree.contains(1) assert tree.contains(2) assert tree.contains(3) assert tree.root.data == 2 # TODO: Test structure of tree after each deletion tree.delete(2) print(tree.root.data) assert tree.contains(1) assert tree.contains(3) assert tree.contains(2) == False tree.delete(1) assert tree.contains(1) == False assert tree.contains(3) assert tree.contains(2) == False tree.delete(3) print(tree) print(tree.root) assert tree.root is None
def test_delete_with_7_items(self): # Create a complete binary search tree of 7 items in level-order items = [4, 2, 6, 1, 3, 5, 7] tree = BinarySearchTree(items) # Test structure of tree after each deletion tree.delete(4) assert tree.size == 6 assert tree.contains(2) is True assert tree.contains(6) is True assert tree.contains(1) is True assert tree.contains(3) is True assert tree.contains(5) is True assert tree.contains(7) is True tree.delete(2) assert tree.size == 5 assert tree.contains(2) is False assert tree.contains(6) is True assert tree.contains(1) is True assert tree.contains(3) is True assert tree.contains(5) is True assert tree.contains(7) is True tree.delete(6) assert tree.size == 4 assert tree.contains(2) is False assert tree.contains(6) is False assert tree.contains(1) is True assert tree.contains(3) is True assert tree.contains(5) is True assert tree.contains(7) is True tree.delete(1) assert tree.size == 3 assert tree.contains(2) is False assert tree.contains(6) is False assert tree.contains(1) is False assert tree.contains(3) is True assert tree.contains(5) is True assert tree.contains(7) is True tree.delete(3) assert tree.size == 2 assert tree.contains(2) is False assert tree.contains(6) is False assert tree.contains(1) is False assert tree.contains(3) is False assert tree.contains(5) is True assert tree.contains(7) is True tree.delete(5) assert tree.size == 1 assert tree.contains(2) is False assert tree.contains(6) is False assert tree.contains(1) is False assert tree.contains(3) is False assert tree.contains(5) is False assert tree.contains(7) is True tree.delete(7) assert tree.size == 0 assert tree.contains(2) is False assert tree.contains(6) is False assert tree.contains(1) is False assert tree.contains(3) is False assert tree.contains(5) is False assert tree.contains(7) is False assert tree.root is None
class Setree: def __init__(self, ls=None): self.tree = BinarySearchTree() self.size = 0 if ls is not None: for item in ls: self.add(item) def add(self, item): '''Add an item to the set. O(log(n))''' if self.contains(item) is False: self.tree.insert(item) self._update_size() def contains(self, item): '''Check if set contains item. O(log(n))''' return self.tree.contains(item) def remove(self, item): '''Remove item from set or ValueError O(log(n))''' self.tree.delete(item) self._update_size() def union(self, nset): '''Create a new set with elements of both sets O((m+n)*log(m+n))''' return Setree(self.in_order() + nset.in_order()) def intersection(self, nset): '''Create a new set with elements that are in both sets O(min(m,n)*log(n)*log(m))''' if self.size > nset.size: small = nset big = self else: big = nset small = self nnset = Setree() for item in small.in_order(): if big.contains(item): nnset.add(item) return nnset def is_subset(self, nset): '''Check if each item in one set is in this set O(n*log(n)*log(m))''' if self.size < nset.size: return False items = nset.in_order() for i in items: if self.contains(i) is False: return False return True def difference(self, nset): '''Check for each item in this is not in another set O(m*log(n))''' return Setree( [i for i in self.in_order() if nset.contains(i) is False]) def in_order(self): """returns all of the values in a set in sorted order O(m)""" return self.tree.items_in_order() def _update_size(self): """Updates the size.""" self.size = self.tree.size def bad_balance(self): print('Previous height:') print(self._height()) queue = LinkedQueue() ls = self.in_order() self._binary_sorter(ls, queue.enqueue) self.tree = BinarySearchTree() count = 1 while queue.front() is not None: self.add(ls[queue.dequeue()]) count += 1 print('Balanced height:') print(self._height()) print(count) def _binary_sorter(self, ls, visit, last=None, left=None, right=None): if left is None: left, right = 0, len(ls) - 1 midpoint = left + ((right - left) // 2) if midpoint != last: visit(midpoint) if midpoint != last: self._binary_sorter(ls, visit, midpoint, midpoint + 1, right) if midpoint != last: self._binary_sorter(ls, visit, midpoint, left, midpoint - 1) def _height(self): return self.tree.root.height_f()
def test_delete_with_7_items(self): # Create a complete binary search tree of 7 items in level-order items = [4, 2, 6, 1, 3, 5, 7] tree = BinarySearchTree(items) # TODO: Test structure of tree after each deletion tree.delete(4) assert tree.contains(2) assert tree.contains(1) assert tree.contains(6) assert tree.contains(3) assert tree.contains(5) assert tree.contains(7) tree.delete(6) assert tree.contains(5) assert tree.contains(3) assert tree.contains(2) assert tree.contains(7) assert tree.contains(1) tree.delete(3) assert tree.contains(2) assert tree.contains(5) assert tree.contains(1) assert tree.contains(7) tree.delete(7) assert tree.contains(2) assert tree.contains(5) assert tree.contains(1) tree.delete(5) assert tree.contains(2) assert tree.contains(1) tree.delete(1) assert tree.contains(2) tree.delete(2) assert tree.root is None
class Set: def __init__(self, items): self.set = BinarySearchTree() self.size = 0 for item in items: self.add(item) def __eq__(self, other): return self.set.items_in_order() == other.set.items_in_order() def contains(self, item): '''Check what items are in the set''' return self.set.contains(item) # Time complexity: O(log n) def add(self, item): '''Add an item to a set''' if self.set.contains(item) != True: self.set.insert(item) self.size += 1 # Time Complexity: O(log n) def remove(self, item): '''Remove item from a set''' return self.set.delete(item) # Time Complexity: O(log n) def union(self, other_set): '''Merges sets together, but removes duplicates''' unionset = Set(self.set.items_in_order() + other_set.set.items_in_order()) return unionset # Time Complexity: O(m + n) def intersection(self, other_set): '''Finds the most common elements in both sets''' # if item is present in self.set and other_set: # return item intersectionitems = [] for item in self.set.items_in_order(): if other_set.contains(item) == True: intersectionitems.append(item) intersectionset = Set(intersectionitems) return intersectionset #check if item is in otherset using contains #if it is add to intersectionitems #create a new set out of intersectionitems and return it def difference(self, other_set): '''Checks what items are present in one set that are not present in the other''' differenceitems = [] for item in self.set.items_in_order(): if other_set.contains(item) == False: differenceitems.append(item) differenceset = Set(differenceitems) return differenceset # Time Complexity: O(m + n) def is_subset(self, other_set): '''Checks if a set is a subset of another set''' for item in self.set.items_in_order(): if other_set.contains(item) == False: return False return True
class Set: def __init__(self, elements=None): self.tree = BinarySearchTree() self.size = 0 if elements is not None: for elm in elements: self.add(elm) def size(self): return self.tree.size def __iter__(self): return self def contains(self, element): if self.tree.contains(element): return True return False def add(self, element): if self.contains(element): raise ValueError("Cannot add element to Set again") else: self.tree.insert(element) self.size += 1 def remove(self, element): self.tree.delete(element) self.size -= 1 def union(self, other_set): """TODO: Running time: O(n*k), have to visit every node TODO: Memory usage: O(n+k) nodes are stored on stack""" result = self.tree.items_in_order() for elm in other_set.tree.items_in_order(): if elm not in result: result.append(elm) return Set(result) def intersection(self, other_set): """TODO: Running time: O(n), have to visit every node TODO: Memory usage: O(n+k) nodes are stored on stack""" result = Set() for elm in self.tree.items_in_order(): if other_set.contains(elm): result.add(elm) return result def difference(self, other_set): """TODO: Running time: O(n), have to visit every node TODO: Memory usage: O(n+k) nodes are stored on stack""" result = Set() for elm in self.tree.items_in_order(): if not other_set.contains(elm): result.add(elm) for elm in other_set.tree.items_in_order(): if elm in result.tree.items_in_order(): result.remove(elm) return result def is_subset(self, other_set): """TODO: Running time: O(n) worst, O(1 best), have to visit every node TODO: Memory usage: O(n+k) nodes are stored on stack""" if self.size > other_set.size: return False for elm in self.tree.items_in_order(): if not other_set.tree.contains(elm): return False return True
class TreeSet(object): """Initialize a new empty set structure, and add each element if a sequence is given""" def __init__(self, elements=None): self.tree = BinarySearchTree() self.element = BinaryTreeNode if elements is not None: for element in elements: self.add(element) @property def size(self): """Property that tracks the number of elements in constant time""" return self.tree.size def contains(self, element): """return a boolean indicating whether element is in this set Running time: O(log n) since we just search through a binary tree""" return self.tree.contains(element) def add(self, element): """add element to this set, if not present already Running time: O(log n) because we must binary search""" if self.tree.contains(element): raise KeyError(f'{element} is already in tree.') else: self.tree.insert(element) def remove(self, element): """remove element from this set, if present, or else raise KeyError Running time: Best Case: O(h) because we have to either scale the tree or move everything down once found. Worst Case: O(n) because the element could be the last we check in the tree.""" if self.tree.contains(element) != True: raise KeyError(f'No such element exists: {element}') else: self.tree.delete(element) def union(self, other_set): """return a new set that is the union of this set and other_set Best and worst case: (m + n) * log m because we add the length of each set to the time of the .add calls""" # m log m + n*2logm = (m + 2n) * log m = # (m + n) * log m new_set = TreeSet() for element in self.tree.items_pre_order(): # O(h(self)) new_set.add(element) # + O(m) # Adds remaining other_set elements for element in other_set.tree.items_pre_order(): if not new_set.contains(element): # O(log m) new_set.add(element) # +O(log m) return new_set def intersection(self, other_set): """return a new set that is the intersection of this set and other_set Best case/worst case: O(log n) due to binary search""" new_set = TreeSet() for element in self.tree.items_in_order(): if other_set.contains(element): new_set.add(element) return new_set def difference(self, other_set): """return a new set that is the difference of this set and other_set Best/Worst case: O(n) because we require checking all nodes""" new_set = TreeSet() for element in self.tree.items_in_order(): new_set.add(element) for element in other_set.tree.items_in_order(): if new_set.contains(element): new_set.remove(element) return new_set def is_subset(self, other_set): """return a boolean indicating whether other_set is a subset of this set Best case:e O(1), incase the size is bigger than other_set and we don't need to do anything. Worst case: O(n), if we must traverse through all nodes""" if self.size > other_set.size: return False for element in other_set.tree.items_in_order(): if not self.contains(element): return False return True
class Set_Tree(object): def __init__(self, elements=None): self.tree = BinarySearchTree() self.size = 0 if elements is not None: for element in elements: self.add(element) def __repr__(self): """Return a string representation of this hash table.""" return 'Set_Tree({!r})'.format(self.tree.items_in_order()) def __iter__(self): for item in self.tree.items_in_order(): yield item def contains(self, element): ''' Return true if this set contains the given element, False otherwise Time complexity: O(logn) Space complexity: O(logn) if recursive, O(1) if iterative ''' return self.tree.contains(element) def add(self, element): ''' Adds element to a set if it's not already in the set Time complexity: O(logn).O(logn) Space complexity: O(1) ''' #if the element is not already in the set, insert it if not self.contains(element): #O(logn) self.tree.insert(element) #O(logn) self.size += 1 def remove(self, element): ''' Removes the element from the set, if it exists Time complexity: O(n) + O(logn) = O(n) Space complexity: O(n) getting the inorder item to find successor ''' #delete from the set, if it's in the set #else raise key error if self.contains(element): #O(1) self.tree.delete(element) #O(n) self.size -= 1 else: raise KeyError def union(self, other_set): ''' Return a new set that is the union of this set and other_set Time complexity: O(mlogm) + O(nlogn) Space complexity: O(m+n) ''' #create a new set with items of self new_set = Set_Tree(self) #O(mlogm) #then add everything from other_set to new_set for element in other_set: #O(n) new_set.add(element) #O(logn) return new_set def intersection(self, other_set): ''' Return a new set that is the intersection of this set and other_set Time complexity: O(mlogn).O(mlog(min(m,n))) Space complexity: O(log(min(m,n))) ''' #create new set new_set = Set_Tree() #O(1) ##########check to see which one is smaller#### #for item in set1 #if it's in set2, add it to the new_set for item in self: #O(m) if other_set.contains(item): #O(logn) new_set.add(item) #O(log(min(m,n))) return new_set def difference(self, other_set): ''' Return a new set that is the difference of this set and other_set Time complexity: O(mlog(mn)) Space complexity: O(log(min(m,n))) ''' #create a new set new_set = Set_Tree() #if item is in set1 but not in set2, add it for item in self: #O(m) if not other_set.contains( item): #O(logn) #if other set doesn't contain item O(logn) new_set.add(item) #O(1) return new_set def is_subset(self, other_set): ''' Return a boolean indicating whether other_set is a subset of this set Time complexity: O(nlogm) Space complexity: O(n) ''' if other_set.size > self.size: #it can't be a subset if it has more items return False for element in other_set: #O(n) if not self.contains(element): #O(logm) return False #not all element of set2 are in set1 return True #finished the loop, all elements are in set1
class TreeSet: def __init__(self, elements=None): """initialize a new empty set structure, and add each element if a sequence is given size - property that tracks the number of elements in constant time""" self.tree = BinarySearchTree() self.size = 0 if elements is not None: for element in elements: self.add(element) def length(self): return self.size def contains(self, element): """return a boolean indicating whether element is in this set""" if self.tree.contains(element): return True # return self.tree.contains(element) is not None return False def add(self, element): """add element to this set, if not present already""" if self.contains(element): raise ValueError('Cannot add element to set again: {}'.format(element)) else: self.tree.insert(element) self.size += 1 def remove(self, element): """remove element from this set, if present, or else raise KeyError""" self.tree.delete(element) self.size -= 1 def union(self, other_set): """return a new set that is the union of this set and other_set""" union_set = self.tree.items_in_order() for element in other_set.tree.items_in_order(): if element not in union_set: union_set.append(element) return TreeSet(union_set) def intersection(self, other_set): """return a new set that is the intersection of this set and other_set""" intersection_set = TreeSet() for element in self.tree.items_in_order(): if other_set.contains(element): intersection_set.add(element) return intersection_set def difference(self, other_set): """return a new set that is the difference of this set and other_set""" difference_set = TreeSet() for element in other_set.tree.items_in_order(): if self.tree.contains(element) is False: difference_set.add(element) for element in self.tree.items_in_order(): if other_set.contains(element) is False: difference_set.add(element) return difference_set def is_subset(self, other_set): """return a boolean indicating whether this set is a subset of other_set""" if other_set.size < self.size: return False for element in self.tree.items_in_order(): if other_set.contains(element) is False: return False return True
class CallRouter(object): def __init__(self, phone_numbers_path, route_prices_path): # Turn txt files (data sources) into python objects self.phone_numbers = self.parse_phone_numbers(phone_numbers_path) # Binary tree of prefixes. Using a BST allows us to find longest prefix as fast as possible self.prefixes = BinarySearchTree() # Contains best price per unique prefix. Using a dict allows quick fetch AND update self.prices = {} # Parse routes which populates self.prefixes and self.prices self.parse_routes(route_prices_path) def turn_txt_file_into_array(self, path_to_file): """Turns txt file into list without '\n'""" file = open(path_to_file, 'r') file_content = file.read() # string representation of .txt file file.close() array = file_content.split('\n') array.pop() # remove last item of array which is empty return array def parse_phone_numbers(self, phone_numbers_path): """Turns txt file into list of phone numbers""" return self.turn_txt_file_into_array(phone_numbers_path) def parse_routes(self, route_prices_path): """ Goes through route_prices_path and creates a binary tree """ # Parse .txt file routes = self.turn_txt_file_into_array(route_prices_path) # For every route for route in routes: # get prefix and price (separated by a comma) prefix, price = route.split(',') if self.prefixes.contains(prefix): # Check if price is cheaper if self.prices[prefix] > price: self.prices[prefix] = price else: # We've never seen prefix before self.prefixes.insert(prefix) # insert prefix into our list of prefixes self.prices[prefix] = price # log the cost for that prefix def get_routing_cost(self, phone_number): """Find longest matching prefix and return cheapest cost Since routes is a binary tree, we only remember cheapest cost for identical prefix""" last_digit_idx = len(str(phone_number)) - 1 # Search for full phone number inside prefix, then remove one digit at a time while last_digit_idx > 0: substring = phone_number[0:last_digit_idx] if self.prefixes.contains(substring): return self.prices[substring] last_digit_idx -= 1 # If we have no matching routes, return 0 else: return 0 def save_routing_costs(self, phone_numbers): result = [] for number in phone_numbers: cost = self.get_routing_cost(number) line = "{},{}".format(number, cost) result.append(line) # save number and cost to text file return result