65

I need to make a two dimensional dictionary in python. e.g. new_dic[1][2] = 5

When I make new_dic = {}, and try to insert values, I get a KeyError:

new_dic[1][2] = 5
KeyError: 1

How to do this?

vucalur
  • 6,007
  • 3
  • 29
  • 46
Saurabh
  • 913
  • 3
  • 12
  • 15
  • There is no accepted answer and I think the author was expecting a nice solution that keeps some properties of multidimensional arrays, but in a sparse setting. Like easy looping on the elements of a row or a column. With nested dictionaries, you can easily select a "row" as an inner dict and then iterate over the inner dict. But it is not symetric, if you want to select a "column" and iterate over the corresponding cells, the code does not look similar at all. – Laurent Lyaudet Jun 28 '22 at 09:55
  • I think a data structure that handles this with a multidimensional get() would really be nice. get([1, 1, None, None, 2]) on a "5-dimensional dict" would return a "slice", a "2-dimensional dict" of it where the values of dimension 1 and 2 are 1, of dimension 5 is 2, and the values of dimensions 3 and 4 are free. get() should either return a cell if 0-dimensional "slice" or an iterator in most cases or a duplicated sub-multi-dict. – Laurent Lyaudet Jun 28 '22 at 10:02

8 Answers8

91

A multi-dimensional dictionary is simply a dictionary where the values are themselves also dictionaries, creating a nested structure:

new_dic = {}
new_dic[1] = {}
new_dic[1][2] = 5

You'd have to detect that you already created new_dic[1] each time, though, to not accidentally wipe that nested object for additional keys under new_dic[1].

You can simplify creating nested dictionaries using various techniques; using dict.setdefault() for example:

new_dic.setdefault(1, {})[2] = 5

dict.setdefault() will only set a key to a default value if the key is still missing, saving you from having to test this each time.

Simpler still is using the collections.defaultdict() type to create nested dictionaries automatically:

from collections import defaultdict

new_dic = defaultdict(dict)
new_dic[1][2] = 5

defaultdict is just a subclass of the standard dict type here; every time you try and access a key that doesn't yet exist in the mapping, a factory function is called to create a new value. Here that's the dict() callable, which produces an empty dictionary when called.

Demo:

>>> new_dic_plain = {}
>>> new_dic_plain[1] = {}
>>> new_dic_plain[1][2] = 5
>>> new_dic_plain
{1: {2: 5}}
>>> new_dic_setdefault = {}
>>> new_dic_setdefault.setdefault(1, {})[2] = 5
>>> new_dic_setdefault
{1: {2: 5}}
>>> from collections import defaultdict
>>> new_dic_defaultdict = defaultdict(dict)
>>> new_dic_defaultdict[1][2] = 5
>>> new_dic_defaultdict
defaultdict(<type 'dict'>, {1: {2: 5}})
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
35

Check it out:

def nested_dict(n, type):
    if n == 1:
        return defaultdict(type)
    else:
        return defaultdict(lambda: nested_dict(n-1, type))

And then:

new_dict = nested_dict(2, float)

Now you can:

new_dict['key1']['key2'] += 5

You can create as many dimensions as you want, having the target type of your choice:

new_dict = nested_dict(3, list)
new_dict['a']['b']['c'].append(5)

Result will be:

new_dict['a']['b']['c'] = [5]
Fancy John
  • 38,140
  • 3
  • 27
  • 27
12

One simple way is to just use tuples as keys to a regular dictionary. So your example becomes this:

new_dic[(1, 2)] = 5

The downside is that all usages have to be with this slightly awkward convention, but if that's OK, this is all you need.

Lars P
  • 796
  • 6
  • 16
  • This is perfect for what I need: `new_dic[("orange", "apple", "coconut")] = {"": ""}` – ktang Dec 09 '20 at 10:08
  • 2
    Good answer. Note that you don't need the parentheses, `new_dic[1,2] = 5` will also use the tuple `(1,2)` as key. – markusk Jan 06 '21 at 12:11
10

Simply, you can use defaultdict

from collections import defaultdict
new_dic = defaultdict(dict)
new_dic[1][2]=5
>>>new_dic
defaultdict(<type 'dict'>, {1: {2: 5}})
itzMEonTV
  • 19,851
  • 4
  • 39
  • 49
6

u can try this, it is even easier if it is string

new_dic = {}
a = 1
new_dic[a] = {}
b = 2
new_dic[a][b] = {}
c = 5
new_dic[a][b]={c}

type

new_dic[a][b]
>>>'5'

For string

new_dic = {}
a = "cat"
new_dic[a] = {}
b = "dog"
new_dic[a][b] = {}
c = 5
new_dic[a][b] = {c}

type

new_dic["cat"]["dog"]
>>>'5'
Dsw Wds
  • 482
  • 5
  • 17
  • Dsw: did a great job here. I was looking for such a solution. I can build a structure based on this acting like a multi-dimensional dictionary and put the values on the second plan. – Bitart May 18 '21 at 22:19
3

Do you mean dict or list?

And if you mean dict do you want the second level to be another dict? or a list?

For a dict to work you need to have declared the keys ahead of time.

So if it's dicts in dicts you need something like this:

new_dic = {}
try:
    new_dic[1][2] = 5
except KeyError:
    new_dic[1] = {2:5}
NDevox
  • 4,056
  • 4
  • 21
  • 36
3

Here is a dictionary that contains another dictionary as the value for key 1:

>>> new_dic = {}
>>> new_dic[1] = {2:5}
>>> new_dic
{1: {2: 5}}

The problem that you had with

new_dic={}
new_dic[1][2]=5

is that new_dic[1] does not exist, so you can't add a dictionary (or anything for that matter) to it.

mhawke
  • 84,695
  • 9
  • 117
  • 138
-2

For project, I needed to have 2D dict of class instances, where indeces are float numbers (coordinates). What I did, was to create 2D dict using default dict, and pass my class name as a type. For ex.:

class myCoordinates:
    def __init__(self, args)
    self.x = args[0]
    self.y = args[1]

and then when I tried to create dictionary:

table = mult_dim_dict(2, myCoordinates, (30, 30))

where function 'mult_dim_dict' defined as:

def mult_dim_dict(dim, dict_type, params):
    if dim == 1:
        if params is not None:
            return defaultdict(lambda: dict_type(params))
        else: 
            return defaultdict(dict_type)
    else:
        return defaultdict(lambda: mult_dim_dict(dim - 1, dict_type, params))

Note: you cannot pass multiple arguments, instead you can pass a tuple, containing all of your arguments. If your class, upon creation, does not need any variables to be passed, 3rd argument of function will be None:

class myCoors:
def __init__(self, tuple=(0, 0)):
    self.x, self.y = tuple

def printCoors(self):
    print("x = %d, y = %d" %(self.x, self.y))


def mult_dim_dict(dim, dict_type, params):
    if dim == 1:
        if params is not None:
            return defaultdict(lambda: dict_type(params))
        else:
            return defaultdict(dict_type)
    else:
        return defaultdict(lambda: mult_dim_dict(dim - 1, dict_type, params))

dict = mult_dim_dict(2, myCoors, None)
dict['3']['2'].x = 3
dict['3']['2'].y = 2

dict['3']['2'].printCoors()  # x = 3, y = 2 will be printed

dict = mult_dim_dict(2, myCoors, (30, 20))
dict['3']['2'].printCoors()  # x = 30, y = 20 will be printed
Miradil Zeynalli
  • 423
  • 4
  • 18