Сортировка справки: сначала по этому, а потом по тому - PullRequest
5 голосов
/ 01 ноября 2009

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

Поле, которое я хочу отсортировать в кортежах, выглядит как "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'), 
]

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

Ответы [ 3 ]

10 голосов
/ 01 ноября 2009
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
8 голосов
/ 01 ноября 2009

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

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

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

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

Достаточно простой подход к решению вашей проблемы - крошечный класс шимов:

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 - всего восемь против 4 + 1 == 5 пользовательских подход сравнения в наиболее компактной форме (то есть тот, который использует либо cmp с изменением знака, либо cmp с замененными аргументами). Три утверждения - не слишком большая цена за преимущества извлечения ключей! -)

Производительность, очевидно, не такая уж большая проблема с таким коротким списком, но с еще более скромным (в 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 ", особенно со всеми другими преимуществами, которые я уже упоминал!

2 голосов
/ 01 ноября 2009
>>> 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')]

Не самое красивое решение в мире ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...