Питон группе
предположим, что у меня есть набор пар данных, где индекс 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')
]
Я хочу сгруппировать их по типу (по 1-й индексированной строке) как таковые:
result = [
{
type:'KAT',
items: ['11013331', '9843236']
},
{
type:'NOT',
items: ['9085267', '11788544']
},
{
type:'ETH',
items: ['5238761', '962142', '7795297', '7341464', '5594916', '1550003']
}
]
Как я могу достичь этого эффективным способом?
5 ответов:
сделай это в 2 шага. Во-первых, создайте словарь.
>>> 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'}]
Примечание оба из них не соблюдают первоначальный порядок ключей. Вам нужен OrderedDict, если вам нужно сохранить порядок.
>>> 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)
. Мы хотим превратить это в список диктов, где " тип "является ключом, а "элементы" - это список 0-х элементов кортежи, возвращаемые values_iterator. Вот так: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
содержится нужный дикт, как указано в вашем вопросе.вы могли бы рассмотреть, хотя, просто сделать один дикт из этого, ключ по типу, и каждое значение, содержащее список значений. В вашей текущей форме, чтобы найти значения для определенного типа, вам придется перебирать список, чтобы найти dict, содержащий соответствующий ключ "type", а затем получить из него элемент "items". Если вы используете один dict вместо списка 1-item dicts, вы можете найти элементы для определенного типа с помощью одного ключевого поиска в главном dict. Используя
groupby
, это будет выглядеть так:result = {} for key,valuesiter in groupby(input, key=sortkeyfn): result[key] = list(v[0] for v in valuesiter)
result
теперь содержит этот дикт (это похоже на промежуточныйres
defaultdict в ответе @KennyTM):{'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'}]