Подтвердить что ты не робот

Группа Python

Предположим, что у меня есть такой набор парных данных, где индекс 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'] 
           }
         ] 

Как я могу достичь этого эффективным способом?

Спасибо

4b9b3361

Ответ 1

Сделайте это за 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'}]

Обратите внимание, что оба из них не соответствуют первоначальному порядку ключей. Вам нужен заказ, если вам нужно сохранить заказ.

>>> 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'}]

Ответ 2

Встроенный модуль itertools Python фактически имеет функцию groupby, которую вы могли бы использовать, но элементы, которые должны быть сгруппированы, должны сначала сортироваться так, что элементы, которые должны быть сгруппированы, смежны в списке:

sortkeyfn = key=lambda s:s[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). Мы хотим превратить это в список dicts, где "тип" - это ключ, а "items" - это список из 0-го элемента кортежей, возвращаемых значением_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, как указано в вашем вопросе.

Возможно, вы можете подумать, просто изложив из этого один диктофон, с ключом по типу и каждое значение, содержащее список значений. В вашей текущей форме, чтобы найти значения для определенного типа, вам придется перебирать список, чтобы найти dict, содержащий соответствующий тип "type", а затем получить элемент "items" из него. Если вы используете один диктофон вместо списка 1-элементных dicts, вы можете найти элементы для определенного типа с одним ключом в главном dict. Используя groupby, это будет выглядеть так:

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

result теперь содержит этот dict (это похоже на промежуточный 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)}

Ответ 3

Следующая функция будет быстро (не сортировать) группы кортежей любой длины ключом, имеющим любой индекс:

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

который не является точно результатом, который вы просили, но может также удовлетворить ваши потребности.

Ответ 4

Мне также понравилась pandas простая группировка. он мощный, простой и наиболее подходящий для большого набора данных

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