2013-06-24 1 views
4

У меня есть группа элементов, которые помечены как item_labels = [('a', 3), ('b', 2), ('c', 1), ('d', 3), ('e', 2), ('f', 3)]Python сортировки списка по размеру группы

Я хочу, чтобы отсортировать их по размеру группы. например, метка 3 имеет размер 3, а этикетка 2 имеет размер 2 в приведенном выше примере.

Я пробовал использовать комбинацию groupby и sorted, но не работал.

In [162]: sil = sorted(item_labels, key=op.itemgetter(1)) 

In [163]: sil 
Out[163]: [('c', 1), ('b', 2), ('e', 2), ('a', 3), ('d', 3), ('f', 3)] 

In [164]: g = itt.groupby(sil,) 
Display all 465 possibilities? (y or n) 

In [164]: g = itt.groupby(sil, key=op.itemgetter(1)) 

In [165]: for k, v in g: 
    .....:  print k, list(v) 
    .....: 
    .....: 
1 [('c', 1)] 
2 [('b', 2), ('e', 2)] 
3 [('a', 3), ('d', 3), ('f', 3)] 

In [166]: sg = sorted(g, key=lambda x: len(list(x[1]))) 

In [167]: sg 
Out[167]: [] # not exactly know why I got an empty list here 

Я всегда могу написать какой-нибудь утомительный цикл для этого, но я бы скорее нашел что-то более элегантное. Любое предложение? Если есть библиотеки, которые мне полезны, я с удовольствием буду их использовать. например, pandas, scipy

ответ

3

В python2.7 и выше, использовать счетчик:

from collections import Counter 
c = Counter(y for _, y in item_labels) 
item_labels.sort(key=lambda t : c[t[1]]) 

В python2.6, для нашей цели, это Counter конструктор может быть реализован с использованием defaultdict (как это было предложено @perreal) таким образом:

from collections import defaultdict 
def Counter(x): 
    d = defaultdict(int) 
    for v in x: d[v]+=1 
    return d 

Поскольку мы работаем только с числами, и предполагая, что число минимальны как в вашем примере, мы действительно можем использовать список (который будет совместим с еще более старой версии Python):

def Counter(x): 
    lst = list(x) 
    d = [0] * (max(lst)+1) 
    for v in lst: d[v]+=1 
    return d 

без рецепта, вы можете просто сделать это:

item_labels.sort(key=lambda t : len([x[1] for x in item_labels if x[1]==t[1] ])) 

Это медленнее, но разумно по сравнению с короткими списками.


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

+0

К сожалению, я использую python 2.6, поэтому не могу использовать 'Counter'. – clwen

+0

Спасибо. Эта строка 'item_labels.sort (key = lambda t: c [t [0]])' должна быть 'item_labels.sort (key = lambda t: c [t [1]])'? – clwen

+0

@clwen да, это опечатка. – Elazar

2

itertools.groupby возвращает итератор, поэтому это для цикла: for k, v in g: фактически потребляет этот итератор.

>>> it = iter([1,2,3]) 
>>> for x in it:pass 
>>> list(it)   #iterator already consumed by the for-loop 
[] 

код:

>>> lis = [('a', 3), ('b', 2), ('c', 1), ('d', 3), ('e', 2), ('f', 3)] 
>>> from operator import itemgetter 
>>> from itertools import groupby 
>>> lis.sort(key = itemgetter(1)) 
>>> new_lis = [list(v) for k,v in groupby(lis, key = itemgetter(1))] 
>>> new_lis.sort(key = len) 
>>> new_lis 
[[('c', 1)], [('b', 2), ('e', 2)], [('a', 3), ('d', 3), ('f', 3)]] 

Чтобы получить сглаженный использование списка itertools.chain:

>>> from itertools import chain 
>>> list(chain.from_iterable(new_lis)) 
[('c', 1), ('b', 2), ('e', 2), ('a', 3), ('d', 3), ('f', 3)] 
3
from collections import defaultdict 
import operator 
l=[('c', 1), ('b', 2), ('e', 2), ('a', 3), ('d', 3), ('f', 3)] 
d=defaultdict(int) 
for p in l: d[p[1]] += 1 
print [ p for i in sorted(d.iteritems(), key=operator.itemgetter(1)) 
     for p in l if p[1] == i[1] ] 
+0

Вы эффективно внедряете 'Counter', используя' defaultdict' – Elazar

+0

@ Elazar: что во многих случаях быстрее, чем дефолт по умолчанию. Попробуй. +1 – dawg

+0

@drewk: ['defaultdict' может быть быстрее, чем' Counter'] (http://stackoverflow.com/a/2525617/4279), хотя в данном случае это не имеет значения – jfs

2

То же @perreal's и @Elazar's ответы, но с лучшими именами:

from collections import defaultdict 

size = defaultdict(int) 
for _, group_id in item_labels: 
    size[group_id] += 1 

item_labels.sort(key=lambda (_, group_id): size[group_id]) 
print item_labels 
# -> [('c', 1), ('b', 2), ('e', 2), ('a', 3), ('d', 3), ('f', 3)] 
1

Вот еще один способ:

example=[('a', 3), ('b', 2), ('c', 1), ('d', 3), ('e', 2), ('f', 3)] 

out={} 
for t in example: 
    out.setdefault(t[1],[]).append(t) 

print sorted(out.values(),key=len) 

Печать:

[[('c', 1)], [('b', 2), ('e', 2)], [('a', 3), ('d', 3), ('f', 3)]] 

Если вы хотите плоский список :

print [l for s in sorted(out.values(),key=len) for l in s] 
[('c', 1), ('b', 2), ('e', 2), ('a', 3), ('d', 3), ('f', 3)]