Сортировка подсписок элементов в списке, оставляющий остальное на месте

Скажем, у меня есть отсортированный список строк, как в:

['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

Теперь я хочу сортировать на основе конечного числового значения для B - поэтому у меня есть:

['A', 'B' , 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

Один из возможных алгоритмов состоит в том, чтобы хешировать регулярное выражение, например regex = re.compile(ur'(B)(\d*)), найти индексы первого и последнего B, нарезать список, отсортировать фрагмент с помощью второй группы регулярных выражений, а затем вставить отсортированный фрагмент. Однако это кажется слишком большим количеством хлопот. Есть ли способ написать ключевую функцию, которая "оставляет элемент на месте", если он не соответствует регулярному выражению и только сортирует элементы (подсписки), которые соответствуют?

Примечание: приведенное выше просто пример; Я не обязательно знаю шаблон (или, возможно, захочу также сортировать C или любую строку, в которой есть завершающий номер). В идеале я ищу подход к общей проблеме сортировки только подпоследовательностей, которые соответствуют заданному критерию (или в противном случае - только те, которые соответствуют конкретному критерию заданного префикса, за которым следует строка цифр).

Ответ 1

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

'AB123' -> ['AB', 123]
'CD'    -> ['CD']
'456'   -> ['', 456]

Примечание.. В последнем случае пустая строка '' не является строго обязательной в CPython 2.x, поскольку целые классы сортируются перед строками - но что деталь реализации, а не гарантия язык, а в Python 3.x требуется , потому что строки и целые числа вообще не могут сравниваться.

Вы можете создать такую ​​ключевую функцию, используя понимание списка и re.split():

import re

def trailing_digits(x):
   return [
       int(g) if g.isdigit() else g
       for g in re.split(r'(\d+)$', x)
   ]

Здесь он находится в действии:

>>> s1 = ['11', '2', 'A', 'B', 'B1', 'B11', 'B2', 'B21', 'C', 'C11', 'C2']

>>> sorted(s1, key=trailing_digits)
['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11']

Как только вы добавите ограничение, которое только строки с определенным префиксом или префиксами имеют отсортированные цифры в конце, все становится немного сложнее.

Следующая функция строит и возвращает ключевую функцию, которая выполняет требование:

def prefixed_digits(*prefixes):
    disjunction = '|'.join('^' + re.escape(p) for p in prefixes)
    pattern = re.compile(r'(?<=%s)(\d+)$' % disjunction)
    def key(x):
        return [
            int(g) if g.isdigit() else g
            for g in re.split(pattern, x)
        ]
    return key

Основное различие заключается в том, что создается предварительно скомпилированное регулярное выражение (содержащее lookbehind, построенное из прилагаемого префикса или префиксов), и возвращается ключевая функция с использованием этого регулярного выражения.

Вот несколько примеров использования:

>>> s2 = ['A', 'B', 'B11', 'B2', 'B21', 'C', 'C11', 'C2', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B', 'C'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C2', 'C11', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B', 'D'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D2', 'D12']

Если вызываемый без аргументов, prefixed_digits() возвращает ключевую функцию, которая ведет себя одинаково с trailing_digits:

>>> sorted(s1, key=prefixed_digits())
['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11']

Предостережения:

  • Из-за ограничения в модуле Python re относительно синтаксиса lookbhehind несколько префиксов должны иметь одинаковую длину.

  • В Python 2.x строки, которые являются чисто числовыми, будут отсортированы численно независимо от того, какие префиксы поставляются в prefixed_digits(). В Python 3 они будут вызывать исключение (кроме случаев, когда вызывается без аргументов или в специальном случае key=prefixed_digits('')), который будет сортировать чисто числовые строки в численном виде и префиксные строки по алфавиту). Фиксация, которая может быть возможна со значительно более сложным регулярным выражением, но я отказался от попытки через двадцать минут.

Ответ 2

Если я правильно понимаю, ваша конечная цель - сортировать подпоследовательности, оставляя в покое элементы, которые не являются частью подпоследовательностей.

В вашем примере подпоследовательность определяется как элементы, начинающиеся с "B" . В вашем списке примеров содержатся элементы в лексикографическом порядке, что слишком удобно, и может отвлекаться от нахождения обобщенного решения. Немного перепутайте, используя другой список примеров. Как насчет:

['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']

Здесь элементы больше не заказываются (по крайней мере, я пытался их организовать, чтобы они не были), ни те, которые начинаются с "B" , ни другие. Однако элементы, начинающиеся с "B" , по-прежнему образуют единую непрерывную подпоследовательность, занимая единый диапазон 1-6, а не разделенные диапазоны, например, как 0-3 и 6-7. Это снова может отвлекать, я рассмотрю этот аспект дальше.

Если я правильно понимаю вашу конечную цель, вы хотите, чтобы этот список отсортировался следующим образом:

['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']

Чтобы выполнить эту работу, нам нужна ключевая функция, которая вернет кортеж, например:

  • Первое значение:
    • Если элемент не начинается с "B" , тогда индекс в исходном списке (или значение в том же порядке)
    • Если элемент начинается с "B" , то индекс последнего элемента, который не начинался с "B"
  • Второе значение:
    • Если элемент не начинается с "B" , то опустите это
    • Если элемент начинается с "B" , то числовое значение

Это можно реализовать следующим образом: doctests:

def order_sublist(items):
    """
    >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2'])
    ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

    >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2'])
    ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']

    """
    def key():
        ord1 = [0]

        def inner(item):
            if not item.startswith('B'):
                ord1[0] += 1
                return ord1[0],
            return ord1[0], int(item[1:] or 0)
        return inner

    return sorted(items, key=key())

В этой реализации элементы сортируются по этим клавишам:

[(1,), (1, 2), (1, 11), (1, 22), (1, 0), (1, 1), (1, 21), (2,), (3,), (4,), (5,)]

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

Эта реализация содержит несколько трюков, которые стоит объяснить:

  • Функция key возвращает кортеж из 1 или 2 элементов, как объяснялось ранее: элементы не-B имеют одно значение, элементы B имеют два.

  • Первое значение кортежа - это не совсем исходный индекс, но он достаточно хорош. Значение перед первым элементом B равно 1, все элементы B используют одно и то же значение, а значения после B каждый раз получают увеличенное значение. Поскольку (1,) < (1, x) < (2,), где x может быть что угодно, эти ключи будут отсортированы по мере их необходимости.

И теперь к "настоящим" трюкам: -)

  • Что происходит с ord1 = [0] и ord1[0] += 1? Это метод изменения нелокального значения в функции. Если бы я использовал просто ord1 = 0 и ord1 += 1, это не сработало бы, потому что ord1 - это примитивное значение, определенное вне функции. Без ключевого слова global оно не будет ни видимым, ни переназначаемым. Примитивное значение ord1 внутри функции inner будет затенять внешнее примитивное значение. Но ord1 является списком, он отображается внутри inner, и его содержимое может быть изменено. Обратите внимание, что нельзя переназначить. Если вы заменили на ord1[0] += 1 как ord1 = [ord1[0] + 1], что приведет к одному и тому же значению, это не сработает, так как в этом случае ord1 с левой стороны является локальной переменной, затеняющей ord1 во внешней области, и не изменяя его значение.

  • Что происходит с функциями key и inner? Я думал, что это будет аккуратно, если ключевая функция, которую мы перейдем на sorted, будет многократно использоваться. Эта более простая версия также работает:

    def order_sublist(items):
        ord1 = [0]
    
        def inner(item):
            if not item.startswith('B'):
                ord1[0] += 1
                return ord1[0],
            return ord1[0], int(item[1:] or 0)
    
        return sorted(items, key=inner)
    

    Важным отличием является то, что если вы хотели бы использовать inner дважды, оба использования будут использовать один и тот же список ord1. Который может быть приемлемым, поскольку длительность целочисленного значения ord1[0] не переполняется во время использования. В этом случае вы не будете использовать эту функцию дважды, и даже если бы вы, вероятно, не столкнулись бы с риском целочисленного переполнения, но, в принципе, приятно сделать функцию чистой и многоразовой, обернув ее как я в моем первоначальном предложении. То, что делает функция key, это просто инициализировать ord1 = [0] в своей области действия, определить функцию inner и вернуть функцию inner. Таким образом, ord1 фактически закрыт, благодаря закрытию. Каждый раз, когда вы вызываете key(), он возвращает функцию, у которой есть ее личное, новое значение ord1.

  • И наконец, обратите внимание на doctests: комментарий """ ... """ - это больше, чем просто документация, это исполняемые тесты. Строки >>> - это код для выполнения в оболочке Python, а следующие строки - ожидаемый вывод. Если у вас есть эта программа в файле с именем script.py, вы можете запустить тесты с помощью python -m doctest script.py. Когда все тесты проходят, вы не получаете выход. Когда тест выходит из строя, вы получаете хороший отчет. Это отличный способ проверить, работает ли ваша программа на примерах. Вы можете иметь несколько тестовых примеров, разделенных пустыми линиями, чтобы охватить интересные угловые случаи. В этом примере есть два тестовых примера: исходный отсортированный вход и измененный несортированный вход.

Однако, поскольку @zero-piraeus сделал интересное замечание:

Я вижу, что ваше решение опирается на sorted() сканирование списка слева направо (что разумно - я не могу представить, что TimSort будет заменен или радикально изменен в ближайшее время - но не гарантируется Python AFAIK, и есть алгоритмы сортировки, которые не работают так).

Я старался быть самокритичным и сомневался, что сканирование слева направо разумно. Но я думаю, что так оно и есть. В конце концов, сортировка действительно происходит на основе ключей, а не фактические значения. Я думаю, что, скорее всего, Python делает что-то вроде этого:

  • Возьмите список значений ключа с помощью [key(value) for value in input], посетив значения слева направо.
  • zip список ключей с оригинальными элементами
  • Применить любой алгоритм сортировки в zipped-списке, сравнивая элементы по первому значению zip и заменяя элементы
  • В конце верните отсортированные элементы с помощью return [t[1] for t in zipped]

При создании списка ключевых значений, он может работать на нескольких потоках, пусть говорят два, первый поток, заполняющий первую половину, и вторую нить, заполняющую вторую половину параллельно. Это испортит трюк ord1[0] += 1. Но я сомневаюсь, что он делает такую ​​оптимизацию, поскольку это просто кажется излишним.

Но чтобы устранить любую тень сомнения, мы можем следовать этой альтернативной стратегии реализации сами, хотя решение становится немного более подробным:

def order_sublist(items):
    """
    >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2'])
    ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

    >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2'])
    ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']

    """
    ord1 = 0
    zipped = []
    for item in items:
        if not item.startswith('B'):
            ord1 += 1
        zipped.append((ord1, item))

    def key(item):
        if not item[1].startswith('B'):
            return item[0],
        return item[0], int(item[1][1:] or 0)

    return [v for _, v in sorted(zipped, key=key)]

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


Что делать, если вам нужен этот список примеров:

['X', 'B', 'B1', 'B11', 'B2', 'B22', 'C', 'Q1', 'C11', 'C2', 'B21']

Чтобы отсортироваться следующим образом:

['X', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'Q1', 'C11', 'C2', 'B22']

То есть элементы, начинающиеся с "B" , отсортированные по их числовому значению, даже если они не образуют непрерывную подпоследовательность?

Это невозможно с помощью магической функции. Это, конечно, возможно, хотя, с некоторыми более legwork. Вы могли:

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

Если вам нужна помощь в этой последней реализации, дайте мне знать.

Ответ 3

Большинство ответов были сосредоточены на B, в то время как мне нужно было более общее решение, как было отмечено. Здесь один:

def _order_by_number(items):
    regex = re.compile(u'(.*?)(\d*)$') # pass a regex in for generality
    keys = {k: regex.match(k) for k in items}
    keys = {k: (v.groups()[0], int(v.groups()[1] or 0)) 
            for k, v in keys.iteritems()}
    items.sort(key=keys.__getitem__)

Я все еще ищу волшебный ключ, но это оставило бы на месте

Ответ 4

Вы можете использовать модуль natsort:

>>> from natsort import natsorted
>>> 
>>> a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
>>> natsorted(a)
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

Ответ 5

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

Вы можете использовать cmp в sorted() -функции вместо key:

s1=['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

def compare(a,b):
    if (a[0],b[0])==('B','B'): #change to whichever condition you'd like
        inta=int(a[1:] or 0)
        intb=int(b[1:] or 0)
        return cmp(inta,intb)  #change to whichever mode of comparison you'd like
    else:
        return 0               #if one of a, b doesn't fulfill the condition, do nothing

sorted(s1,cmp=compare)

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

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

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

s1=['11', '2', 'A', 'B', 'B11', 'B21', 'B1', 'B2', 'C', 'C11', 'C2', 'B09','C8','B19']

def cond1(a):           #change this to whichever condition you'd like
    return a[0]=='B'

def comparison(a,b):    #change this to whichever type of comparison you'd like to make
    inta=int(a[1:] or 0)
    intb=int(b[1:] or 0)
    return cmp(inta,intb)

def n2CompareSort(alist,condition,comparison):
    for i in xrange(len(alist)):
        for j in xrange(i):
            if condition(alist[i]) and condition(alist[j]):
                if comparison(alist[i],alist[j])==-1:
                    alist[i], alist[j] = alist[j], alist[i]  #in-place swap

n2CompareSort(s1,cond1,comparison)

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

Ответ 6

Вы можете использовать следующую ключевую функцию. Он вернет кортеж формы (letter, number), если есть число или форма (letter,), если числа нет. Это работает с ('A',) < ('A', 1).

import re

a = ['A', 'B' ,'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

regex = re.compile(r'(\d+)')

def order(e):
    num = regex.findall(e)
    if num:
        num = int(num[0])
        return e[0], num
    return e,

print(sorted(a, key=order))
>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

Ответ 7

Если я понимаю ваш вопрос ясно, вы пытаетесь сортировать массив по двум атрибутам; алфавит и конечный 'номер.

Вы можете просто сделать что-то вроде

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
data.sort(key=lambda elem: (elem[0], int(elem[1:]))

но поскольку это вызовет исключение для элементов без числа, завершающего их, мы можем продолжить и просто сделать функцию (мы не должны использовать лямбда в любом случае!)

def sortKey(elem):
     try:
             attribute = (elem[0], int(elem[1:]))
     except:
             attribute = (elem[0], 0)

     return attribute

С помощью этой ключевой функции сортировки мы можем отсортировать элемент на месте с помощью

data.sort(key=sortKey)

Кроме того, вы можете просто перейти к настройке функции sortKey, чтобы придать приоритет определенным алфавитам, если хотите.

Ответ 8

Чтобы ответить именно на то, что вы описали, вы можете это сделать:

l = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2', 'D']


def custom_sort(data, c):
    s = next(i for i, x in enumerate(data) if x.startswith(c))
    e = next((i for i, x in enumerate(data) if not x.startswith(c) and i > s), -1)
    return data[:s] + sorted(data[s:e], key=lambda d: int(d[1:] or -1)) + data[e:]


print(custom_sort(l, "B"))

Если вы, какой полный вид, можете просто сделать это (как @Mike JS Choi ответил, но проще):

output = sorted(l, key=lambda elem: (elem[0], int(elem[1:] or -1)))

Ответ 9

Вы можете использовать ord() для преобразования для примера "B11" в числовом значении:

cells = ['B11', 'C1', 'A', 'B1', 'B2', 'B21', 'B22', 'C11', 'C2', 'B']
conv_cells = []

## Transform expression in numerical value.
for x, cell in enumerate(cells):
    val = ord(cell[0]) * (ord(cell[0]) - 65) ## Add weight to ensure respect order.
    if len(cell) > 1:
        val += int(cell[1:])
    conv_cells.append((val, x)) ## List of tuple (num_val, index).

## Display result.
for x in sorted(conv_cells):
    print(str(cells[x[1]]) + ' - ' + str(x[0]))

Ответ 10

Если вы хотите сортировать разные правила для разных подгрупп, вы можете использовать кортежи в качестве сортировочных ключей. В этом случае элементы будут сгруппированы и отсортированы по слоям: сначала с помощью элемента первого кортежа, затем в каждой подгруппе вторым элементом кортежа и т.д. Это позволяет нам иметь разные правила сортировки в разных подгруппах. Единственный предел - предметы должны быть сопоставимы в каждой группе. Например, вы не можете иметь ключи типа int и str в той же подгруппе, но вы можете иметь их в разных подгруппах.

Попробуем применить его к задаче. Мы подготовим кортежи с типами элементов (str, int) для элементов B и кортежей с (str, str) для всех остальных.

def sorter(elem):
    letter, num = elem[0], elem[1:]
    if letter == 'B':
        return letter, int(num or 0)  # hack - if we've got `''` as num, replace it with `0`
    else:
        return letter, num

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

sorted(data, key=sorter)
# returns
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

UPDATE

Если вы предпочитаете его в одной строке:

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

sorted(data, key=lambda elem: (elem[0],  int(elem[1:] or 0) if elem[0]=='B' else elem[:1]
# result
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

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

Ответ 11

import numpy as np

def sort_with_prefix(list, prefix):
    alist = np.array(list)
    ix = np.where([l.startswith(prefix) for l in list])

    alist[ix] =  [prefix + str(n or '')
             for n in np.sort([int(l.split(prefix)[-1] or 0) 
                              for l in alist[ix]])]
    return alist.tolist()

Например:

l = ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

print(sort_with_prefix(l, 'B'))
>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

Ответ 12

Используя только ключ и предварительное условие, что последовательность уже "отсортирована":

import re

s = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

def subgroup_ordinate(element):
    # Split the sequence element values into groups and ordinal values.
    # use a simple regex and int() in this case
    m = re.search('(B)(.+)', element)  
    if m:
        subgroup = m.group(1)
        ordinate = int(m.group(2))
    else:
        subgroup = element
        ordinate = None
    return (subgroup, ordinate)

print sorted(s, key=subgroup_ordinate)

['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

Функция subgroup_ordinate() выполняет две функции: идентифицирует группы, подлежащие сортировке, а также определяет порядковый номер внутри групп. В этом примере используется регулярное выражение, но функция может быть произвольно сложной. Например, мы можем изменить его на ur'(B|C)(.+)' и отсортировать последовательности B и C.

['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

Внимательно прочитав вопрос о щедрости, я отмечаю, что требование "сортирует некоторые значения, оставляя других" на месте ". Определение функции сравнения для возврата 0 для элементов, которые не входят в подгруппы, оставит эти элементы там, где они были в последовательности.

s2 = ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11']

def compare((_a,a),(_b,b)):
    return 0 if a is None or b is None else cmp(a,b)

print sorted(s, compare, subgroup_ordinate)

['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11']

Ответ 13

def compound_sort(input_list, natural_sort_prefixes=()):
    padding = '{:0>%s}' % len(max(input_list, key=len))
    return sorted(
        input_list, 
        key = lambda li: \
            ''.join(
                [li for c in '_' if not li.startswith(natural_sort_prefixes)] or 
                [c for c in li if not c.isdigit()] + \
                [c for c in padding.format(li) if c.isdigit()]
            )
        )

Этот метод сортировки получает:

  • input_list: list для сортировки,
  • natural_sort_prefixes: A string или tuple of string s.

Элементы списка, нацеленные на natural_sort_prefixes, будут отсортированы естественным образом. Элементы, не соответствующие этим префиксам, будут отсортированы лексикографически.

Этот метод предполагает, что элементы списка структурированы как один или несколько нечисловых символов, за которыми следуют одна или несколько цифр.

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

Вы можете использовать его как:

print compound_sort(['A', 'B' , 'B11', 'B1', 'B2', 'C11', 'C2'], natural_sort_prefixes=("A","B"))

# ['A', 'B', 'B1', 'B2', 'B11', 'C11', 'C2']

Ответ 14

import re
from collections import OrderedDict

a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
dict = OrderedDict()

def get_str(item):
  _str = list(map(str, re.findall(r"[A-Za-z]", item)))
  return _str

def get_digit(item):
  _digit = list(map(int, re.findall(r"\d+", item)))
  return _digit

for item in a:
  _str = get_str(item)
  dict[_str[0]] = sorted([get_digit(dig) for dig in a if _str[0] in dig])

nested_result = [[("{0}{1}".format(k,v[0]) if v else k) for v in dict[k]] for k in dict.keys()]
print (nested_result)
# >>> [['A'], ['B', 'B1', 'B2', 'B11', 'B21', 'B22'], ['C', 'C1', 'C2', 'C11']]

result = []
for k in dict.keys():
  for v in dict[k]:
    result.append("{0}{1}".format(k,v[0]) if v else k)
print (result)
# >>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

Ответ 15

Если вы хотите отсортировать произвольное подмножество элементов, оставив другие элементы на месте, может быть полезно создать представление над исходным списком. Идея взгляда в целом заключается в том, что он похож на объектив над исходным списком, но его изменение будет манипулировать базовым исходным списком. Рассмотрим этот вспомогательный класс:

class SubList:
    def __init__(self, items, predicate):
        self.items = items
        self.indexes = [i for i in range(len(items)) if predicate(items[i])]

    @property
    def values(self):
        return [self.items[i] for i in self.indexes]

    def sort(self, key):
        for i, v in zip(self.indexes, sorted(self.values, key=key)):
            self.items[i] = v

Конструктор сохраняет исходный список в self.items и исходные индексы в self.indexes, как определено predicate. В ваших примерах функция predicate может быть такой:

def predicate(item):
    return item.startswith('B')

Затем свойство values является объективом над исходным списком, возвращая список значений, выбранных из исходного списка исходными индексами.

Наконец, функция sort использует self.values для сортировки, а затем изменяет исходный список.

Рассмотрим эту демонстрацию с помощью доктрин:

def demo(values):
    """
    >>> demo(['X', 'b3', 'a', 'b1', 'b2'])
    ['X', 'b1', 'a', 'b2', 'b3']

    """
    def predicate(item):
        return item.startswith('b')
    sub = SubList(values, predicate)

    def key(item):
        return int(item[1:])
    sub.sort(key)

    return values

Обратите внимание, что SubList используется только как инструмент, с помощью которого можно манипулировать входом values. После вызова sub.sort изменяется values, с элементами для сортировки, выбранными функцией predicate, и сортируется в соответствии с функцией key, а все остальные элементы никогда не перемещаются.

Используя этот помощник SubList с соответствующими функциями predicate и key вы можете сортировать произвольный выбор элементов списка.