예제 #1
0
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)
예제 #2
0
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")
예제 #3
0
 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()
예제 #5
0
 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
예제 #6
0
 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()
예제 #8
0
 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
예제 #9
0
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
예제 #11
0
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
예제 #12
0
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
예제 #13
0
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
예제 #14
0
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