Python:从非唯一键和值列表创建列表字典的一行代码

Python: One-Liner to create a dictionary of lists from a list of non-unique keys and values

在我的编程经验中,我经常想从一个列表创建一个列表字典,就像这样:

输入:键值对列表,具有非唯一键,例如[("a", 1), ("a", 2), ("b", 3)]
输出:一个字典,其中每个非唯一键都有一个值列表,如 {"a": [1,2], "b": [3]}.

现在我知道我可以像这样达到我想要的结果:

list_of_elems = [("a", 1), ("a", 2), ("b", 3)]
dict_of_elems = {}
for key, val in list_of_elems:
    if key in dict_of_elems:
        dict_of_elems[key].append(val)
    else:
        dict_of_elems[key] = [val]

(是的,我跳到这里之前看了看,但 EAFP 看起来基本一样)。

这很好用,但它是 6 行!我确信 python 中一定有一种方法可以使智能字典理解成为单行代码,但我想不出一个!有人有好主意吗?

实现这一目标的任何一行都将难以阅读并且可能很慢 (O(n^2))。我建议只使用您拥有的逻辑编写和重用函数。

我没有单行本,但这里有一个更简洁的版本:

list_of_elems = [("a", 1), ("a", 2), ("b", 3)]
dict_of_elems = {}
for key, val in list_of_elems:
    dict_of_elems.setdefault(key, []).append(val)

一个 2 衬里,但缺点是 map 会产生一个与输入大小相等的无关列表。

dict_of_elms = {}                                                    # output dict
list(                                                                # force generator to be run
    map(                                                             # apply lambda to every element
        lambda tupElm : d[tupElm[0]].append(tupElm[1])               # append to corresponding dict key or create new list
            if dict_of_elms.get(tupElm[0], None) 
            else dict_of_elms.setdefault(tupElm[0], [tupElm[1]]),
        list_of_items                                                # list of size-two tuples to iterate on
    )
)

还值得注意的是,尽管可以在两行内压缩,但无论如何 space 都更容易...

一个班轮可以是:dict((k, [v for (kk, v) in list_of_elems if kk==k]) for (k,_ ) in list_of_elems)

然而正如 robinsax 所说,复杂性是最差的,Secret Agent 解决方案更可取。

这个 2-liner 使用 2 passes:

in_d = [("a", 1), ("a", 2), ("b", 3)]
output_dict = dict((e[0], []) for e in in_d)
_ = [output_dict[k].append(v) for (k, v) in in_d]

output_dict
# {'a': [1, 2], 'b': [3]}

Edit 正如正确指出的那样,上面修改字典作为副作用,更好的形式是实现显式循环:

in_d = [("a", 1), ("a", 2), ("b", 3)]
output_dict = dict((e[0], []) for e in in_d)
for (k, v) in in_d:
    output_dict[k].append(v)

output_dict
# {'a': [1, 2], 'b': [3]}

使用 defaultdict 可以一次通过:

from collections import defaultdict
ddict = defaultdict(list)
for (k, v) in in_d:
    ddict[k].append(v)

ddict
# {'a': [1, 2], 'b': [3]}

我唯一能想到的 one liner(使用普通工具而不使用副作用理解)是使用 groupby:

list_of_elems = [("a", 1), ("a", 2), ("b", 3)]

di={k:[t[1] for t in v] for k,v in groupby(sorted(list_of_elems),key=lambda t:t[0])}

>>> di
{'a': [1, 2], 'b': [3]}

算上就是两行from itertools import groupby。由于 sorted 它比惯用语慢:

di={}
for k,v in list_of_elems:
    di.setdefault(k, []).append(v)

defaultdict:

from collections import defaultdict

di=defaultdict(list)
for k,v in list_of_elems:
    di[k].append(v)

>>> di
defaultdict(<class 'list'>, {'a': [1, 2], 'b': [3]})

或子类 dict 以便默认行为是 return 缺少键的列表(这实际上是 .setdefaultdefaultdict 方法正在做的):

class Mydict(dict):
    def __missing__(self, key):
        self[key]=[]
        return self[key]

di=Mydict()
for k,v in list_of_elems:
    di[k].append(v)