bgneal@12: """ Code example from Complexity and Computation, a book about
bgneal@12: exploring complexity science with Python.  Available free from
bgneal@12: 
bgneal@12: http://greenteapress.com/complexity
bgneal@12: 
bgneal@12: Copyright 2011 Allen B. Downey.
bgneal@12: Distributed under the GNU General Public License at gnu.org/licenses/gpl.html.
bgneal@12: """
bgneal@12: 
bgneal@12: import string
bgneal@12: 
bgneal@12: 
bgneal@12: class LinearMap(object):
bgneal@12:     """A simple implementation of a map using a list of tuples
bgneal@12:     where each tuple is a key-value pair."""
bgneal@12: 
bgneal@12:     def __init__(self):
bgneal@12:         self.items = []
bgneal@12: 
bgneal@12:     def add(self, k, v):
bgneal@12:         """Adds a new item that maps from key (k) to value (v).
bgneal@12:         Assumes that they keys are unique."""
bgneal@12:         self.items.append((k, v))
bgneal@12: 
bgneal@12:     def get(self, k):
bgneal@12:         """Looks up the key (k) and returns the corresponding value,
bgneal@12:         or raises KeyError if the key is not found."""
bgneal@12:         for key, val in self.items:
bgneal@12:             if key == k:
bgneal@12:                 return val
bgneal@12:         raise KeyError
bgneal@12: 
bgneal@12: 
bgneal@12: class BetterMap(object):
bgneal@12:     """A faster implementation of a map using a list of LinearMaps
bgneal@12:     and the built-in function hash() to determine which LinearMap
bgneal@12:     to put each key into."""
bgneal@12: 
bgneal@12:     def __init__(self, n=100):
bgneal@12:         """Appends (n) LinearMaps onto (self)."""
bgneal@12:         self.maps = []
bgneal@12:         for i in range(n):
bgneal@12:             self.maps.append(LinearMap())
bgneal@12: 
bgneal@13:     def __len__(self):
bgneal@13:         """Returns the number of LinearMap buckets in the map."""
bgneal@13:         return len(self.maps)
bgneal@13: 
bgneal@12:     def find_map(self, k):
bgneal@12:         """Finds the right LinearMap for key (k)."""
bgneal@12:         index = hash(k) % len(self.maps)
bgneal@12:         return self.maps[index]
bgneal@12: 
bgneal@12:     def add(self, k, v):
bgneal@12:         """Adds a new item to the appropriate LinearMap for key (k)."""
bgneal@12:         m = self.find_map(k)
bgneal@12:         m.add(k, v)
bgneal@12: 
bgneal@12:     def get(self, k):
bgneal@12:         """Finds the right LinearMap for key (k) and looks up (k) in it."""
bgneal@12:         m = self.find_map(k)
bgneal@12:         return m.get(k)
bgneal@12: 
bgneal@14:     def iteritems(self):
bgneal@14:         """Generator function to yield items in the map."""
bgneal@14:         for lm in self.maps:
bgneal@14:             for t in lm.items:
bgneal@14:                 yield t
bgneal@14: 
bgneal@12: 
bgneal@12: class HashMap(object):
bgneal@12:     """An implementation of a hashtable using a BetterMap
bgneal@12:     that grows so that the number of items never exceeds the number
bgneal@12:     of LinearMaps.
bgneal@12: 
bgneal@12:     The amortized cost of add should be O(1) provided that the
bgneal@12:     implementation of sum in resize is linear."""
bgneal@12: 
bgneal@12:     def __init__(self):
bgneal@12:         """Starts with 2 LinearMaps and 0 items."""
bgneal@12:         self.maps = BetterMap(2)
bgneal@12:         self.num = 0
bgneal@12: 
bgneal@12:     def get(self, k):
bgneal@12:         """Looks up the key (k) and returns the corresponding value,
bgneal@12:         or raises KeyError if the key is not found."""
bgneal@12:         return self.maps.get(k)
bgneal@12: 
bgneal@12:     def add(self, k, v):
bgneal@12:         """Resize the map if necessary and adds the new item."""
bgneal@13:         if self.num == len(self.maps):
bgneal@12:             self.resize()
bgneal@12: 
bgneal@12:         self.maps.add(k, v)
bgneal@12:         self.num += 1
bgneal@12: 
bgneal@12:     def resize(self):
bgneal@12:         """Makes a new map, twice as big, and rehashes the items."""
bgneal@12:         new_maps = BetterMap(self.num * 2)
bgneal@12: 
bgneal@14:         for k, v in self.maps.iteritems():
bgneal@14:             new_maps.add(k, v)
bgneal@12: 
bgneal@12:         self.maps = new_maps
bgneal@12: 
bgneal@12: 
bgneal@12: def main(script):
bgneal@12:     m = HashMap()
bgneal@12:     s = string.ascii_lowercase
bgneal@12: 
bgneal@12:     for k, v in enumerate(s):
bgneal@12:         m.add(k, v)
bgneal@12: 
bgneal@12:     for k in range(len(s)):
bgneal@12:         print k, m.get(k)
bgneal@12: 
bgneal@12: 
bgneal@12: if __name__ == '__main__':
bgneal@12:     import sys
bgneal@12:     main(*sys.argv)