巨蟒小 M

假设我有一组数据对,其中 指数0是值,索引1是类型:

input = [
('11013331', 'KAT'),
('9085267',  'NOT'),
('5238761',  'ETH'),
('5349618',  'ETH'),
('11788544', 'NOT'),
('962142',   'ETH'),
('7795297',  'ETH'),
('7341464',  'ETH'),
('9843236',  'KAT'),
('5594916',  'ETH'),
('1550003',  'ETH')
]

我想按照它们的类型(按照第一个索引字符串)对它们进行分组,如下所示:

result = [
{
'type': 'KAT',
'items': ['11013331', '9843236']
},
{
'type': 'NOT',
'items': ['9085267', '11788544']
},
{
'type': 'ETH',
'items': ['5238761', '962142', '7795297', '7341464', '5594916', '1550003']
}
]

我如何才能有效地做到这一点?

221999 次浏览

分两步做。首先,创建一个字典。

>>> input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'), ('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')]
>>> from collections import defaultdict
>>> res = defaultdict(list)
>>> for v, k in input: res[k].append(v)
...

然后,将该字典转换为期望的格式。

>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}]

使用 itertools.groupby 也是可以的,但是首先需要对输入进行排序。

>>> sorted_input = sorted(input, key=itemgetter(1))
>>> groups = groupby(sorted_input, key=itemgetter(1))
>>> [{'type':k, 'items':[x[0] for x in v]} for k, v in groups]
[{'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}]

注意,这两种方法都不遵循键的原始顺序。如果需要保持订单,则需要 OrderedDect。

>>> from collections import OrderedDict
>>> res = OrderedDict()
>>> for v, k in input:
...   if k in res: res[k].append(v)
...   else: res[k] = [v]
...
>>> [{'type':k, 'items':v} for k,v in res.items()]
[{'items': ['11013331', '9843236'], 'type': 'KAT'}, {'items': ['9085267', '11788544'], 'type': 'NOT'}, {'items': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}]

Python 的内置 itertools模块实际上有一个 groupby函数,但是为了实现这一点,必须首先对要分组的元素进行排序,使得要分组的元素在列表中是连续的:

from operator import itemgetter
sortkeyfn = itemgetter(1)
input = [('11013331', 'KAT'), ('9085267', 'NOT'), ('5238761', 'ETH'),
('5349618', 'ETH'), ('11788544', 'NOT'), ('962142', 'ETH'), ('7795297', 'ETH'),
('7341464', 'ETH'), ('9843236', 'KAT'), ('5594916', 'ETH'), ('1550003', 'ETH')]
input.sort(key=sortkeyfn)

现在输入如下:

[('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'),
('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH'), ('11013331', 'KAT'),
('9843236', 'KAT'), ('9085267', 'NOT'), ('11788544', 'NOT')]

groupby返回一个由2个元组组成的序列,格式为 (key, values_iterator)。我们想要的是将其转换成一个以‘ type’为键的 dicts 列表,而‘ item’是 value _ iterator 返回的元组的第0个元素的列表。像这样:

from itertools import groupby
result = []
for key,valuesiter in groupby(input, key=sortkeyfn):
result.append(dict(type=key, items=list(v[0] for v in valuesiter)))

现在 result包含您想要的结果,如您的问题中所述。

但是,您可以考虑仅仅使用这个命令来做一个结论,按类型键入,并且每个值都包含值列表。在当前表单中,要查找特定类型的值,必须遍历列表以查找包含匹配的“ type”键的 dit,然后从中获取“ item”元素。如果使用单个 dict 而不是由1个条目组成的 dict 列表,那么可以通过对主 dict 进行单键查找来找到特定类型的条目。使用 groupby,这看起来像:

result = {}
for key,valuesiter in groupby(input, key=sortkeyfn):
result[key] = list(v[0] for v in valuesiter)

result现在包含这个结论(这类似于@KennyTM 回答中的中间 res默认结论) :

{'NOT': ['9085267', '11788544'],
'ETH': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'],
'KAT': ['11013331', '9843236']}

(如果你想把它简化成一句话,你可以:

result = dict((key,list(v[0] for v in valuesiter)
for key,valuesiter in groupby(input, key=sortkeyfn))

或者使用新奇的词汇理解形式:

result = {key:list(v[0] for v in valuesiter)
for key,valuesiter in groupby(input, key=sortkeyfn)}

下面的函数将快速(需要 没有分类)组元组的任何长度的键有任何索引:

# given a sequence of tuples like [(3,'c',6),(7,'a',2),(88,'c',4),(45,'a',0)],
# returns a dict grouping tuples by idx-th element - with idx=1 we have:
# if merge is True {'c':(3,6,88,4),     'a':(7,2,45,0)}
# if merge is False {'c':((3,6),(88,4)), 'a':((7,2),(45,0))}
def group_by(seqs,idx=0,merge=True):
d = dict()
for seq in seqs:
k = seq[idx]
v = d.get(k,tuple()) + (seq[:idx]+seq[idx+1:] if merge else (seq[:idx]+seq[idx+1:],))
d.update({k:v})
return d

对于您的问题,您希望按键进行分组的索引为1,因此:

group_by(input,1)

给予

{'ETH': ('5238761','5349618','962142','7795297','7341464','5594916','1550003'),
'KAT': ('11013331', '9843236'),
'NOT': ('9085267', '11788544')}

虽然不是你要求的输出,但也能满足你的需要。

我也喜欢熊猫简单的 分组。它的功能强大,简单,最适合大型数据集

result = pandas.DataFrame(input).groupby(1).groups

result = []
# Make a set of your "types":
input_set = set([tpl[1] for tpl in input])
>>> set(['ETH', 'KAT', 'NOT'])
# Iterate over the input_set
for type_ in input_set:
# a dict to gather things:
D = {}
# filter all tuples from your input with the same type as type_
tuples = filter(lambda tpl: tpl[1] == type_, input)
# write them in the D:
D["type"] = type_
D["itmes"] = [tpl[0] for tpl in tuples]
# append D to results:
result.append(D)


result
>>> [{'itmes': ['9085267', '11788544'], 'type': 'NOT'}, {'itmes': ['5238761', '5349618', '962142', '7795297', '7341464', '5594916', '1550003'], 'type': 'ETH'}, {'itmes': ['11013331', '9843236'], 'type': 'KAT'}]

这个答案类似于 @ PaulMcG 的回答,但不需要对输入进行排序。

对于那些进入函数式编程的人来说,groupBy可以写成一行(不包括导入!)与 itertools.groupby不同的是,它不需要对输入进行排序:

from functools import reduce # import needed for python3; builtin in python2
from collections import defaultdict


def groupBy(key, seq):
return reduce(lambda grp, val: grp[key(val)].append(val) or grp, seq, defaultdict(list))

(lambda... or grp的原因是,为了使 reduce()工作,lambda需要返回它的第一个参数; 因为 list.append()总是返回 None,所以 or总是返回 grp。也就是说,这是一种绕过 python 限制的方法,即 lambda 只能计算单个表达式。)

这将返回一个 dict,其键是通过计算给定函数找到的,其值是原始顺序中原始项的列表。对于 OP 的示例,将其调用为 groupBy(lambda pair: pair[1], input)将返回以下结论:

{'KAT': [('11013331', 'KAT'), ('9843236', 'KAT')],
'NOT': [('9085267', 'NOT'), ('11788544', 'NOT')],
'ETH': [('5238761', 'ETH'), ('5349618', 'ETH'), ('962142', 'ETH'), ('7795297', 'ETH'), ('7341464', 'ETH'), ('5594916', 'ETH'), ('1550003', 'ETH')]}

而且根据 @ PaulMcG 的回答的要求,OP 的格式可以通过将其包装在一个列表内涵中来找到。这样就可以了:

result = {key: [pair[0] for pair in values],
for key, values in groupBy(lambda pair: pair[1], input).items()}

您可以使用 工具库为您的确切任务生成特别代码,并允许动态代码生成。

from convtools import conversion as c


# grouping by second elements of tuples;
# aggregate defines the schema of the expected output elements
converter = c.group_by(c.item(1)).aggregate({
"type": c.item(1),
"items": c.ReduceFuncs.Array(c.item(0)),
}).gen_converter()


# now you have a function which does what you asked,
# store it somewhere for further reuse
converter(input_data)

遵循 Snippet 也是获得期望结果的一种方法-

res = []
dict1 = {}
for item in input:
if item[1] not in dict1:
dict1[item[1]] = [item[0]]
elif item[1] in dict1:
dict1[item[1]].append(item[0])
for k, v in dict1.items():
res.append({"type": k, "items": v})




# res = [ { type:'KAT', items: ['11013331', '9843236'] },{ type:'NOT',  items: ['9085267', '11788544'] },{ type:'ETH',  items: ['5238761', '962142', '7795297', '7341464', '5594916', '1550003'] }]

这不是非常有效,但它是 Python 的。基本上,通过获取组值的集合来确定不同的组,然后对于这些组中的每一个,获取该组中的项。

[
{
"type": group,
"items": [item[0] for item in input if item[1] == group]
}
for group in {item[1] for item in input}
]