2009-11-01 3 views
5

У меня есть список кортежей, которые я пытаюсь сортировать и могу использовать некоторую помощь.Помогите сортировать: сначала этим, а затем этим

Поле, в котором я хочу сортировать по кортежам, выглядит «XXX_YYY». Во-первых, я хочу группировать значения XXX в обратном порядке, а затем в этих группах я хочу поместить значения YYY в обычный порядок сортировки. (Примечание: Я просто счастлив, на самом деле, сортировка второго элемента в кортеже таким образом, обратный порядок первого слово, нормальный порядок второго.)

Вот пример того, что у меня есть и что Я бы хотел в конце концов ... не уверен, как это сделать.

mylist = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

Я хотел бы выполнить какую-то sort() в этом списке, который изменит вывод на:

sorted = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

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

ответ

8

Пользовательские сравнения функции сортировки, как это было предложено в существующих ответах, действительно позволяют легко сортировать в смеси восходящего и убывающие заказы - но они имеют серьезные проблемы с производительностью и были удалены в Python 3, оставив только предпочтительный подход к настройке - пользовательский key-extract функции ... гораздо более быстрые, хотя и более деликатные для использования в относительно редких случаях использования смешанных по возрастанию/убыванию.

В Python 2.*, который поддерживает любой вид настройки (не как в одном вызове к sort или sorted :-), функция пользовательского сравнения может быть передан в качестве аргумента cmp= имени; или, пользовательская функция извлечения ключа может быть передана как аргумент key= с именем. В Python 3.* доступен только последний вариант.

Это определенно стоит понять подход к изъятию ключа, даже если вы считаете, что вместо этого вы решили только проблему с использованием пользовательского сравнения: не только для производительности, но и для будущей проверки (Python 3) и для общности (подход key= также применим к min, max, itertools.groupby ... гораздо более общий, чем подход cmp=!).

Key-extract очень просто, когда все ключевые подполя сортируются одинаково (все восходящие или все нисходящие) - вы просто извлекаете их; это все еще довольно легко, если подполя, который идет «по-другому», это числа (вы просто меняете свой знак при извлечении); деликатный случай - это именно тот, который у вас есть - несколько строковых полей, которые должны сравниваться в противоположных направлениях.

Разумно простой подход к решению вашей проблемы крошечного класс регулировочной прокладки:

class Reverser(object): 
    def __init__(self, s): self.s = s 
    def __lt__(self, other): return other.s < self.s 
    def __eq__(self, other): return other.s == self.s 

Обратите внимание, что у вас есть только поставить __lt__ и __eq__< и == операторах) - sort и друзья синтезировать все другие при необходимости, на основе этих двух.

Итак, вооружившись этим небольшим вспомогательным инструментом, мы можем приступить легко ...:

def getkey(tup): 
    a, b = tup[0].split('_') 
    return Reverser(a), b 

my_list.sort(key=getkey) 

Как вы видите, как только вы «получить» реверс и основные концепции извлечения, вы платите по существу нет цены используя извлечение ключа вместо выборочного сравнения: код, который я предлагаю, - это 4 оператора для класса реверса (который вы можете написать один раз и помещать в свой «мешок с мешками» где-то), три для функции извлечения ключей и, конечно, один для sort или sorted call - всего восемь против 4 + 1 == 5 пользовательского метода сравнения в самой компактной форме (т.е. тот, который использует либо cmp с изменением знака, либо cmp с замененным аргументом NTS). Три заявления не являются большой ценой, чтобы заплатить за преимущества ключевого извлечения! -)

Производительность явно не большая проблема с таким коротким списком, но с еще более скромным (10 раз) одним ...:

# my_list as in the Q, my_cmp as per top A, getkey as here 

def bycmp(): 
    return sorted(my_list*10, cmp=my_cmp) 

def bykey(): 
    return sorted(my_list*10, key=getkey) 

... 

$ python -mtimeit -s'import so' 'so.bykey()' 
1000 loops, best of 3: 548 usec per loop 
$ python -mtimeit -s'import so' 'so.bycmp()' 
1000 loops, best of 3: 995 usec per loop 

Т.е., key= подхода уже демонстрирует прирост производительности почти в два раза (сортировка списка в два раза быстрее) при работе в списке 50-ти пунктов - стоит скромной цены «8 линий, а чем 5 ", особенно со всеми другими преимуществами, о которых я уже упоминал!

+0

Вау, мне нравится ваше решение. Я не знал, что подход cmp = имел такой штраф. –

+0

@Steven, tx - yep, не все понимают, почему cmp = был удален в Python 3 (как «привлекательная неприятность», которая соблазняет людей страдать от штрафа за производительность!), Именно поэтому я разместил это подробное объяснение, поэтому спасибо для подтверждения это может помочь! -) –

+2

@Alex: Я стесняюсь отредактировать один из * ваших * ответов, но, возможно, my_list.key (cmp = my_cmp) должен быть my_list.sort (key = getkey)? –

10
def my_cmp(x, y): 
    x1, x2 = x[0].split('_') 
    y1, y2 = y[0].split('_') 
    return -cmp(x1, y1) or cmp(x2, y2) 

my_list = [ 
    (u'community_news', u'Community: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_video', u'Community: Video'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_magazine', u'KF: Magazine') 
] 

sorted_list = [ 
    (u'kf_magazine', u'KF: Magazine'), 
    (u'kf_news', u'KF: News & Information'), 
    (u'kf_video', u'KF: Video'), 
    (u'community_news', u'Community: News & Information'), 
    (u'community_video', u'Community: Video'), 
] 

my_list.sort(cmp=my_cmp) 
assert my_list == sorted_list 
+1

Я как раз собирался отредактировать мой, чтобы отменить вызов cmp, когда вы отправили свой ответ. :) – Kylotan

+1

Я упростил сравнение далее с '-cmp (x1, y1) или cmp (x2, y2)'. :) –

+0

вы также можете передать ключевой аргумент для сортировки и избавления от разделения в верхней части вашей функции: my_list.sort (cmp = my_cmp, key = lambda x: x [0] .split ('_')) –

2
>>> def my_cmp(tuple_1, tuple_2): 
    xxx_1, yyy_1 = tuple_1[0].split('_') 
    xxx_2, yyy_2 = tuple_2[0].split('_') 
    if xxx_1 > xxx_2: 
     return -1 
    elif xxx_1 < xxx_2: 
     return 1 
    else: 
     return cmp(yyy_1, yyy_2) 


>>> import pprint 
>>> pprint.pprint(sorted(mylist, my_cmp)) 
[(u'kf_magazine', u'KF: Magazine'), 
(u'kf_news', u'KF: News & Information'), 
(u'kf_video', u'KF: Video'), 
(u'community_news', u'Community: News & Information'), 
(u'community_video', u'Community: Video')] 

Не хорошенькое решения в мире ...

Смежные вопросы