# file: dictionary.py # author: Bob Muller # # CS1101 Computer Science I # # Code showing the use of list and tuples to represent dictionaries. # # A dictionary is a set of associations between keys and values: # # "Bob" => 59, "Joe" => 21, "Mary" => 22, ... # # A dictionary requires three main operations: # # empty : void -> dictionary # add : key -> value -> dictionary -> dictionary # find : key -> dictionary -> value # # We'll look at two implementations of dictionaries, one using # a list of (key, value) pairs (AKA an association list) and # another using binary search trees. For the former, the add # operation requires only 1 unit of work but the find operation # is slow, requiring on the order of N units of work. # For the binary search tree implementation, both add and find # are fast, requiring on the order of log_2 N units of work on # average. # # We'll use strings for keys and integers for values for now. # # type key = string # type value = int ######### The sequential association list representation ########## # # A dictionary is a list of key/value 2-tuples (pairs). # # type dictionary = (key * value) list # d0 = [('Joe', 2135555555), ('Mary', 617552800)] # empty : void -> dictionary # def empty(): return [] # add : key * value * dictionary -> dictionary # # This version of add works in unit time. # def add(key, value, dictionary): return [(key, value)] + dictionary # find : key * dictionary -> value # # The call find(key, dictionary) returns that value associated with # key in the dictionary if key actually occurs in the dictionary. If # key isn't in the dictionary, find returns None. With this # implementation of dictionaries, the find operation requires N units # of work on average. # def find(key, dictionary): for (k, value) in dictionary: if key == k: return value return None # findR : key * dictionary -> value # # NB: find could have been written recursively as follows. But # in Python, this will be wasteful of space because the list # slicing (below) in dictionary[1:] makes a fresh copy of the # dictionary. # def findR(key, dictionary): if dictionary == []: return None else: (k, value) = dictionary[0] if key == k: return value else: findR(key, dictionary[1:]) # MUTABILITY - if we are using mutable dictionaries, add mutates the # dictionary passed as an argument. It returns nothing. # # addM : key * value * dictionary -> void # def addM(key, value, dictionary): dictionary.append((key, value)) return None ######### The binary tree representation ########################## # # A dictionary is either: # 1. None, or # 2. a 4-tuple (dictionary, key, value, dictionary) # empty : void -> dictionary # def empty(): return None # add : key * value * dictionary -> dictionary # # The call add(key, value, dictionary) extends dictionary with # an assocation between key and value. With this implementation # of dictionaries, the add operation requires log_2 N units of # work on average, where N is the number of key/value assocations # in the dictionary. # def add(key, value, dictionary): if dictionary == None: return (None, key, value, None) else: (left, k, val, right) = dictionary # grab the 4 parts if key > k: newRight = add(key, value, right) return (left, k, val, newRight) else: newLeft = add(key, value, left) return (newLeft, k, val, right) # find : key * dictionary -> value # # The call find(key, dictionary) returns that value associated with key # in the dictionary if key actually occurs in the dictionary. If key # isn't in the dictionary, find returns None. With this implementation # of dictionaries, the find operation requires log_2 N units of work # on average. # def find(key, dictionary): if dictionary == None: return None else: (left, k, value, right) = dictionary if key == k: return value else: if key > k: return find(key, right) else: return find(key, left)