I needed several multi-dimensional hashes (or dictionaries as python calls them) and got tired of the long way of doing it, so I ended up coding a simple solution. Turns out this is already solved in Python 2.5 with defaultdict, but I didn’t know that at the time and this ended up being simple and small, so I’m sticking with it for now.
A multi-dimensional dictionary (MDD from here on) is a dictionary whose values are themselves dictionaries. For example, you could store car details in a MDD such that the first dimension is the make and the inner dimension is the model: car_details['Make']['Model']
car_details['Ford']['Mustang'] = "something"
car_details['Toyota']['Corolla'] = "something else"
The class that allows us to simplify building MDDs is a dictionary with a default. This class returns a default value when you lookup a key that’s not set in the dictionary. So if the default is “x” and you lookup a random key that’s not set in the dict you get “x” back.
We can create multi-dimensional dictionaries with this by setting the default value for the outer dimensions to return a dictionary. This way you can forget about the check-if-key-exists-set-if-not business for the outer dimensions and deal only with the innermost dimension.
Here’s my implementation of the dictionary with default. Python 2.5 has this built in with defaultdict, and there’s a recipe on the Python cookbook that may be a better implementation (I haven’t looked at it very much):
class Ddict(dict):
def __init__(self, default=None):
self.default = default
def __getitem__(self, key):
if not self.has_key(key):
self[key] = self.default()
return dict.__getitem__(self, key)
Not a whole lot that’s interesting there. The only thing to pay attention to is that you define the default as a function that creates a value as opposed to the value itself. This seems a little odd but makes sense if you think of how assignment works with arrays and hashes – namely that if we used self[key] = self.default instead of self[key] = self.default() we’d end up with all of our default keys pointing to the same array or hash:
>>> x = []
>>> y = x
>>> y.append('a')
>>> x
['a']
Ok, now to declare and use the MDD:
>>> car_details = Ddict( dict )
>>> car_details['Ford']['Mustang'] = "red"
>>> car_details['Ford']['Taurus'] = "blue"
>>> car_details['Toyota']['Corolla'] = "white"
>>> car_details
{'Toyota': {'Corolla': 'white'}, 'Ford': {'Mustang': 'red', 'Taurus': 'blue'}}
Here are a couple of more examples:
by_user = Ddict( list ) # 1D dictionary with array values
by_user_cmd = Ddict( lambda: Ddict( list ) ) # 2D dictionary with array values
user_totals = Ddict( lambda: Ddict( lambda: 0.0 ) ) # 2D dictionary with float values
user_cmd_totals = Ddict( lambda: Ddict( lambda: Ddict( lambda: 0.0 ) ) ) # 3D dictionary with float values
The lambda business, btw, is simply a way of turning what comes after it into a function. Instead of lambda: Ddict( list ) you could write a function that returns a Ddict( list ) using def, but that’s be more work and longer.