# Making a 2D dict readable

I am building a 2d dictionary with an altered list at the end Afterwords I want to find the min of each list. Is there a better way to do it? Better being faster or at least less of an eyesore.

#dummy function, will be replaced later so don't worry about it.
def alter(x, y, z,):
a = list(x)
for i, val in enumerate(x):
a[i] = val + y + z
return a

masterStats = {}
for numberServed in range(3):
masterStats[numberServed] = {}
for timeToServe in range(3):

for numberServed in masterStats.keys():
for timeToServe in masterStats[numberServed].keys():
print(min(masterStats[numberServed][timeToServe]))

for numberServed in masterStats.keys():
print(numberServed)
for timeToServe in masterStats[numberServed].keys():
print("    " + str(timeToServe))
print("        " + str(masterStats[numberServed][timeToServe]))


Python's variable names are conventionally named lowercase_width_underscores.

Loop using .items() not .keys() if you will need the values, so your final loop can be:

for numberServed, numberServedStats in masterStats.items():
print(numberServed)
for timeToServe, timeToServeStats in numberServedStats,items():
print("    " + str(timeToServe))
print("        " + str(timeToServeStats))


The whole thing can be made more efficient by using numpy:

askStats = numpy.array([0, 1, 2])

y = numpy.arange(3)[:, None, None] # put number served in the first dimension
z = numpy.arange(3)[None, :, None] # put time to serve in the second dimension

stats = x + y + z # same as in alter


We create a new array by adding together three existing arrays. The [:,None, None] parts control how they are added. Essentially, the colon indicates the column the data will be spread across. The None indicates where the data will be duplicated. So numpy.arange(3)[:,None] gets treated like

[
[0, 0, 0],
[1, 1, 1],
[2, 2, 2]
]


Whereas numpy.arange(3)[None, :] gets treated like

[
[0, 1, 2],
[0, 1, 2],
[0, 1, 2]
]


This lets us do the complete loop and adjust function in just that one expression.

for line in stats.min(axis=2).flatten():
print(line)


The min method reduces the 3 dimensional array to 2d array by minimizing along dimension 2. Flatten() converts that 2d array into one long one dimensional array.

for row_index, row in enumerate(stats):
print(row_index)
for col_index, col in enumerate(row):
print("    ", col_index)
print("        ", col)


It probably won't help much here, but for bigger cases using numpy will be more efficient.

• You lost me on that second part, can you explain a bit of what you're doing with numpy and flatten? – EasilyBaffled Jun 7 '13 at 0:09
• @EasilyBaffled, I've added some explanation. – Winston Ewert Jun 7 '13 at 0:24
• So to alter it to accept larger data sets, I replace 3 with x in numpy.arange(x) but how do I alter the [None,:,None] to take any amount? – EasilyBaffled Jun 7 '13 at 1:12
• And how is the alter function run in it? – EasilyBaffled Jun 7 '13 at 1:20
• @EasilyBaffled, edited to hopefully clarify things a bit. – Winston Ewert Jun 7 '13 at 3:43

alter is equivalent to a list comprehension:

def alter(x, y, z):
return [val + y + z for val in x]


collections.defaultdict(dict) is a convenient 2-level dictionary. You could also make use of itertools.product for 2D looping:

masterStats = collections.defaultdict(dict)
for numberServed, timeToServe in itertools.product(range(3), range(3)):


Iterate over values():

for subdict in masterStats.values():
for lst in subdict.values():
print(min(lst))


Iterate over items(), like Winston already mentioned:

for numberServed, subdict in masterStats.items():
print(numberServed)
for timeToServe, lst in subdict.items():
print("    " + str(timeToServe))
print("        " + str(lst))