用多种理解填充字典

Populate a dictionary with multiple comprehensions

我正在尝试创建一个看起来像这样的字典(部分示例):

{(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'}

对于一些更聪明的解决方案来说似乎是一个问题的关键逻辑是一组值分布在 3 个键上,另一组值分布在 2 个键上,第三组每个值有一个键。

与其多次编写重复项,我宁愿通过某种理解以编程方式创建它。

USE = dict([(n, 1), "Residential"] for n in range(1, 4))

这很有效,例如,创建它的第一部分。我能做到:

USE2 = dict([(n, 2), "Crafts"] for n in range(1, 4))
USE.update(USE2)

对于常量来说,这是一种凌乱、不优雅且糟糕的风格。

我对任何其他策略都不知所措。

我已经尝试了一些形式的串行理解:

USE = dict([(n, 1), "Residential"] for n in range(1, 4),
           [(n, 2), "Crafts"] for n in range(1, 4),...

但这失败了,因为 dict() 只接受一个参数。

我做不到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)),...

因为我不明白的原因,该样式仅限于字符串。使用字符串需要稍后转换将用于引用字典的随机数,这似乎更令人头疼(尽管我想这取决于是否有一个优雅的解决方案)。

而且 dict comprehension 似乎不够稳健(这里有完整数据:前 6 个是后 6 个的 3 倍):

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)}

由于列表大小,这最终离开了我的 6x6 边界。如果我对 x 取模,它最终会覆盖条目。似乎我必须编写一个专门的函数来涵盖古怪的 3/2/1 重复,这对于通过某种理解应该可能的事情来说似乎是不必要的。

有什么方法可以在一行中声明如此复杂的字典吗?如果不是,声明这种常量的正确样式是什么?

俗话说:

Don't write programs, write programs that write programs.

我的建议是启动一个交互式会话(或 IPython),创建部分字典并使用 update 合并它们。

然后打印出合并后的 dict 并将其粘贴到您的源代码中。

如果您正在寻找单行理解,像这样的东西怎么样?

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) ))

那个密钥方案有点神秘。这是一种稍微浓缩它的方法。让 (n0, n1) 成为给定类别名称的键。对于每个名字,n1 是常量,但 n0 覆盖了一个连续的范围,所以我们可以只存储该范围的第一项和最后一项。然后我们使用带有双 for 循环的字典理解将压缩版本扩展为您真正想要的字典。

# 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},')

(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',

这是使用传统 for 循环的 dict comp 的等价物。您可能会发现此版本更具可读性。

out = {}
for name, (first, last, n1) in table.items():
    for n0 in range(first, last + 1):
        out[n0, n1] = name

您描述的二维结构实际上非常适合 numpy 数组:

>>> 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)

请注意这行读起来多么自然:创建一个数组,重新整形为 3 行,每行 6 列,垂直(轴 0)重复第一个元素 3 次,第二个元素两次,最后一个元素一次。

结果:

>>> 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)

这几乎是正确的,只是索引是从零开始的,所以我们需要添加一个虚拟列和一个虚拟行:

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)

现在,这 "duck" 等同于您的字典,在查找方面。例如:

>>> result[2,1]  # result[(2,1)] also works
'Residential'