Simple Multi-Dimensional Dictionaries in Python

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.

8 Comments so far

  1. Justin on July 17th, 2007

    I’m a newbie at Python – got the multidimensional dictionary to work (thanks so much for such a clear & concise solution to a common problem). I was trying to figure out how to do an N-D dictionary without the lambda functions (I love them, but I can’t pickle the function…). I can get the one to work, giving me a 2D, but I can’t figure out how to nest them. Could anyone help? Greatly appreciated.

  2. sam on June 3rd, 2008

    I’m a newbie as well. My programming language is Perl. But I’m trying to switch into programming in Python. One of the things I miss about Perl which I can’t find in Python is autovivification of hash of hashes (or dictionary of dictionaries in python i suppose). This seems to be the solution. But what I want to find out is how to automatically increment entries like:

    car_details['Ford']['Mustang'] = 1
    car_details['Toyota']['Corolla'] = 1

    So in the above, I want to count the number for each one. In perl all I need to do is:

    $car_details->{’Ford’}->{’Mustang’} ;
    $car_details->{’Toyota’}->{’Corolla’} ;

    Can you give me some idea?

    Thanks,
    Sam

  3. Alexander Mikhalev on August 29th, 2008

    To Sam:
    car_details['Ford']['Mustang'] = 1 will increment value

  4. Alexander Mikhalev on September 8th, 2008

    Plus sign was eaten in comments. It should be plus equal one.

  5. [...] find some custom classes via google, but Django won’t iterate over them. A couple hours later I figure I need help. I [...]

  6. Jonathan on November 6th, 2009

    I’m restricted to python 2.4 and was wondering how can I sort by a particular key/value pair using the technique here which makes the dictionaries a class?

    Thanks for your help!

  7. Parand on November 6th, 2009

    Jonathan, what do you mean by sort? Are you looking to access the dictionary in key sort order? The above class is still just a dictionary underneath, so you could do the normal key access functions on it; something like:

    keys = car_details.keys()
    keys.sort()
    for k in keys: print car_details[k]

  8. Jonathan on November 6th, 2009

    Hi Parand,

    Perhaps an example will help,

    mydict =
    {’WILW1′: {’fx’: ‘8.1′, ‘obtime’: ‘2009-11-07 06:45:00′, ‘ob’: ‘6.9′},
    ‘GRRW1′: {’fx’: ‘12.8′, ‘obtime’: ‘2009-11-07 04:15:00′, ‘ob’: ‘6.7′},
    ‘NASW1′: {’fx’: ‘6.8′, ‘obtime’: ‘2009-11-07 06:30:00′, ‘ob’: ‘7.1′}
    }

    I would like to sort this by the values of the ‘ob’ key. In this case, this would become:

    mysorteddic =
    {’NASW1′: {’fx’: ‘6.8′, ‘obtime’: ‘2009-11-07 06:30:00′, ‘ob’: ‘7.1′},
    ‘WILW1′: {’fx’: ‘8.1′, ‘obtime’: ‘2009-11-07 06:45:00′, ‘ob’: ‘6.9′},
    ‘GRRW1′: {’fx’: ‘12.8′, ‘obtime’: ‘2009-11-07 04:15:00′, ‘ob’: ‘6.7′}
    }

    I’ve gone into the keys once and then again,

    for k in keys:
    for k2 in keys2:
    if k2 == ‘ob’:
    append to array or something…?
    but it seems like I would have to keep track of the inner keys in an array, and then by that point I completely confuse myself. Thanks for your help!

    –Jonathan

Leave a Reply