Reputation: 515
I'm trying to create a dictionary that looks something like this (a partial example):
{(1, 1): 'Residential', (2, 1): 'Residential', (3, 1): 'Residential', (1, 2): 'Crafts', (2, 2): 'Crafts', (3, 2): 'Crafts', (4, 1): 'Law, Government', (5, 1): 'Law, Government', (4, 2): 'Public Space', (5, 2): 'Public Space', (6, 1): 'Vice', (6, 2): 'Entertainment'}
The critical logic that seems to be a problem for some cleverer solutions is that one set of values are spread across 3 keys, another set is spread across 2 keys, and a third set has one key each.
Instead of writing the duplicates multiple times, I'd rather create it programmatically with some kind of comprehension.
USE = dict([(n, 1), "Residential"] for n in range(1, 4))
This works great, for example, to create the first part of it. I could do:
USE2 = dict([(n, 2), "Crafts"] for n in range(1, 4))
USE.update(USE2)
This is messy, inelegant, and poor style for constants.
I'm at a loss for any other strategy.
I've tried some forms of serial comprehension:
USE = dict([(n, 1), "Residential"] for n in range(1, 4),
[(n, 2), "Crafts"] for n in range(1, 4),...
But this fails because dict() doesn't take more than one argument.
I can't do Python 3 unpacking generalizations:
USE = dict(**dict([(n, 1), "Residential"] for n in range(1, 4)),
**dict([(n, 2), "Crafts"] for n in range(1, 4)),...
Because that style is restricted to strings for reasons I don't understand. Using strings would require later conversion of the random numbers that will be used to reference the dict, and seems like more of a headache (although, I suppose that depends on if there's an elegant solution at all).
And a dict comprehension doesn't seem robust enough (full data here: where the first 6 are 3 times as common as the last 6):
space = ["Residential", "Crafts", "Labor", "Shops", "Trade", "Hospitality",
"Law, Government", "Public Space", "Power", "Manufacture", "Transportation", "Leisure",
"Vice", "Entertainment", "Storage", "Cultivation", "Academic", "Artists"]
TRY = {(y, x+1): s for x, s in enumerate(space) for y in range(1, 7)}
This ends up leaving my 6x6 bounds due to the list size. If I modulo x, it ends up overwriting entries. It seems like I'd have to write a specialized function in order to cover the quirky 3/2/1 repetition, which seems unnecessary for what should be possible via some kind of comprehension.
Is there some way to declare a dictionary with this complexity in a single line? If not, what's the proper style to declare this kind of constant?
Upvotes: 0
Views: 102
Reputation: 53029
The 2D structure you are describing is actually a good fit for numpy arrays:
>>> import numpy as np
>>>
>>> space = ["Residential", "Crafts", "Labor", "Shops", "Trade", "Hospitality",
... "Law, Government", "Public Space", "Power", "Manufacture", "Transportation", "Leisure",
... "Vice", "Entertainment", "Storage", "Cultivation", "Academic", "Artists"]
>>>
>>> sparr = np.array(space, dtype=object).reshape(3,6).repeat((3,2,1), axis=0)
Please note how natural this line reads: make an array, reshape to 3 rows, 6 columns each, repeat vertically (axis 0) the first element 3 times, the second twice and the last once.
Result:
>>> sparr
array([['Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
['Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
['Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
['Law, Government', 'Public Space', 'Power', 'Manufacture', 'Transportation', 'Leisure'],
['Law, Government', 'Public Space', 'Power', 'Manufacture', 'Transportation', 'Leisure'],
['Vice', 'Entertainment', 'Storage', 'Cultivation', 'Academic', 'Artists']], dtype=object)
This is almost correct, only indexing is zero-based, so we need to add one dummy column and one dummy row:
result = np.full((7, 7), None, dtype=object)
>>> result[1:, 1:] = sparr
>>>
>>> result
array([[None, None, None, None, None, None, None],
[None, 'Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
[None, 'Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
[None, 'Residential', 'Crafts', 'Labor', 'Shops', 'Trade', 'Hospitality'],
[None, 'Law, Government', 'Public Space', 'Power', 'Manufacture', 'Transportation', 'Leisure'],
[None, 'Law, Government', 'Public Space', 'Power', 'Manufacture', 'Transportation', 'Leisure'],
[None, 'Vice', 'Entertainment', 'Storage', 'Cultivation', 'Academic', 'Artists']], dtype=object)
Now, this is "duck"-equivalent to your dictionary, lookup-wise. For example:
>>> result[2,1] # result[(2,1)] also works
'Residential'
Upvotes: 1
Reputation: 55469
That key scheme is a bit arcane. Here's one way to condense it a little. Let (n0, n1) be a key for a given category name. For each name, n1 is constant, but n0 covers a contiguous range, so we can just store the first and last items of that range. Then we use a dict comprehension with a double for
loop to expand the condensed version into the dict that you actually want.
# Condensed key table.
# Each name in the full table has a 2-tuple key (n0, n1)
# The tuples in `table` are (first_n0, last_n0, n1)
table = {
'Residential': (1, 3, 1),
'Crafts': (1, 3, 2),
'Law, Government': (4, 5, 1),
'Public Space': (4, 5, 2),
'Vice': (6, 6, 1),
'Entertainment': (6, 6, 2),
}
out = {(n0, n1): name for name, (first, last, n1) in table.items()
for n0 in range(first, last + 1)
}
for k, v in out.items():
print(f'{k}: {v!r},')
out
(1, 1): 'Residential',
(2, 1): 'Residential',
(3, 1): 'Residential',
(1, 2): 'Crafts',
(2, 2): 'Crafts',
(3, 2): 'Crafts',
(4, 1): 'Law, Government',
(5, 1): 'Law, Government',
(4, 2): 'Public Space',
(5, 2): 'Public Space',
(6, 1): 'Vice',
(6, 2): 'Entertainment',
Here's the equivalent of that dict comp using traditional for
loops. You may find this version a little more readable.
out = {}
for name, (first, last, n1) in table.items():
for n0 in range(first, last + 1):
out[n0, n1] = name
Upvotes: 0
Reputation: 326
If you're looking for a one-liner comprehension, how about something like this?
a = (3,'Residential',3,'Crafts',2,'Law, Government',2,'Public Space',1,'Vice',1,'Entertainment')
use = dict()
use.update(dict([(n / 2 + 1, s), a[n+1]] for s in range(1, 4) for n in range(0, len(a), 2) ))
Upvotes: 0
Reputation: 43495
As the saying goes:
Don't write programs, write programs that write programs.
What I would suggest is to start an interactive session (or IPython), create the partial dicts and use update
to merge them.
Then print out the combined dict
and paste it into your source code.
Upvotes: 1