У Python есть встроенная функция для естественной сортировки строк?

Используя Python 3.x, у меня есть список строк, для которых я хотел бы выполнить естественный алфавитный вид.

Естественная сортировка: порядок сортировки файлов в Windows.

Например, следующий список естественно отсортирован (что я хочу):

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] 

И вот «отсортированная» версия вышеуказанного списка (что у меня есть):

 ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'] 

Я ищу функцию сортировки, которая ведет себя как первая.

  • Удаление элементов из списка в Python
  • Как читать числа из файла в Python?
  • Угадайте оптимизацию числа игр (пользователь создает номер, компьютерные догадки)
  • python 3.5: TypeError: необходим байтоподобный объект, а не «str» при записи в файл
  • Невозможно загрузить видео с YouTube
  • Подматрица списка списков (без numpy)
  • python concurrent.futures.ProcessPoolExecutor: Производительность .submit () vs .map ()
  • Заполнение словаря из списка
  • 12 Solutions collect form web for “У Python есть встроенная функция для естественной сортировки строк?”

    Для этого есть сторонняя библиотека для PyPI, называемая natsort (полное раскрытие, я автор пакета). В вашем случае вы можете выполнить одно из следующих действий:

     >>> from natsort import natsorted, ns >>> x = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'] >>> natsorted(x, key=lambda y: y.lower()) ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] >>> natsorted(x, alg=ns.IGNORECASE) # or alg=ns.IC ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] 

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


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

     >>> from natsort import natsort_keygen, ns >>> l1 = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] >>> l2 = l1[:] >>> natsort_key1 = natsort_keygen(key=lambda y: y.lower()) >>> l1.sort(key=natsort_key1) >>> l1 ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] >>> natsort_key2 = natsort_keygen(alg=ns.IGNORECASE) >>> l2.sort(key=natsort_key2) >>> l2 ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] 

    Попробуй это:

     import re def natural_sort(l): convert = lambda text: int(text) if text.isdigit() else text.lower() alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] return sorted(l, key = alphanum_key) 

    Вывод:

     ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] 

    Смотрите, как он работает в Интернете: идеон .

    Код, адаптированный отсюда: Сортировка для людей: естественный порядок сортировки .

    Вот еще более питоническая версия ответа Марка Байера:

     import re def natural_sort_key(s, _nsre=re.compile('([0-9]+)')): return [int(text) if text.isdigit() else text.lower() for text in re.split(_nsre, s)] 

    Теперь эта функция может использоваться как ключ в любой функции, которая ее использует, например list.sort , list.sort , max и т. Д.

    Как лямбда:

     lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)] 

    Я написал функцию, основанную на http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html, которая добавляет возможность по-прежнему передавать свой собственный параметр «ключ». Мне нужно это, чтобы выполнить естественный вид списков, содержащих более сложные объекты (а не только строки).

     import re def natural_sort(list, key=lambda s:s): """ Sort the list into natural alphanumeric order. """ def get_alphanum_key_func(key): convert = lambda text: int(text) if text.isdigit() else text return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))] sort_key = get_alphanum_key_func(key) list.sort(key=sort_key) 

    Например:

     my_list = [{'name':'b'}, {'name':'10'}, {'name':'a'}, {'name':'1'}, {'name':'9'}] natural_sort(my_list, key=lambda x: x['name']) print my_list [{'name': '1'}, {'name': '9'}, {'name': '10'}, {'name': 'a'}, {'name': 'b'}] 
     data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10'] 

    Давайте проанализируем данные. Емкость всех элементов равна 2. И есть 3 буквы в общей литеральной части 'elm' .

    Таким образом, максимальная длина элемента равна 5. Мы можем увеличить это значение, чтобы убедиться (например, до 8).

    Учитывая это, у нас есть однострочное решение:

     data.sort(key=lambda x: '{0:0>8}'.format(x).lower()) 

    без регулярных выражений и внешних библиотек!

     print(data) >>> ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'elm13'] 

    Объяснение:

     for elm in data: print('{0:0>8}'.format(elm).lower()) >>> 0000elm0 0000elm1 0000elm2 0000elm9 000elm10 000elm11 000elm13 

    Данный:

     data=['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'] 

    Подобно решению SergO, 1- футовый без внешних библиотек будет :

     data.sort(key=lambda x : int(x[3:])) 

    или

     sorted_data=sorted(data, key=lambda x : int(x[3:])) 

    Объяснение:

    Это решение использует ключевую функцию сортировки для определения функции, которая будет использоваться для сортировки. Поскольку нам известно, что каждой записи данных предшествует «elm», функция сортировки преобразует в целую часть строки после третьего символа (т. Е. Int (x [3:])). Если числовая часть данных находится в другом месте, то эта часть функции должна измениться.

    ура

    И теперь для чего-то более элегантного (пифонического) – просто прикосновение

    Там есть много реализаций, и, хотя некоторые из них приблизились, ни один из них не захватил элегантность современного python.

    • Протестировано с использованием python (3.5.1)
    • Включен дополнительный список, чтобы продемонстрировать, что он работает, когда цифры являются средней строкой
    • Однако не проверял, что я предполагаю, что если ваш список был значительным, было бы более эффективно компилировать регулярное выражение заранее
      • Я уверен, что кто-то исправит меня, если это ошибочное предположение

    Quicky

     from re import compile, split dre = compile(r'(\d+)') mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)]) 

    Полный код

     #!/usr/bin/python3 # coding=utf-8 """ Natural-Sort Test """ from re import compile, split dre = compile(r'(\d+)') mylist = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13', 'elm'] mylist2 = ['e0lm', 'e1lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm', 'e01lm'] mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)]) mylist2.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)]) print(mylist) # ['elm', 'elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] print(mylist2) # ['e0lm', 'e1lm', 'e01lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm'] 

    Внимание при использовании

    • from os.path import split
      • вам нужно будет дифференцировать импорт

    Вдохновение от

    • Документация по Python-Сортировка КАК
    • Сортировка для людей: естественный порядок сортировки
    • Сортировка людей
    • Авторы / Комментаторы по этому вопросу и ссылки на них

    Один из вариантов – превратить строку в кортеж и заменить цифры с помощью расширенной формы. http://wiki.answers.com/Q/What_does_expanded_form_mean

    таким образом, a90 станет («a», 90,0), а a1 станет («a», 1)

    ниже приведен пример кода кода (который не очень эффективен из-за того, что он удаляет ведущие 0 из чисел)

     alist=["something1", "something12", "something17", "something2", "something25and_then_33", "something25and_then_34", "something29", "beta1.1", "beta2.3.0", "beta2.33.1", "a001", "a2", "z002", "z1"] def key(k): nums=set(list("0123456789")) chars=set(list(k)) chars=chars-nums for i in range(len(k)): for c in chars: k=k.replace(c+"0",c) l=list(k) base=10 j=0 for i in range(len(l)-1,-1,-1): try: l[i]=int(l[i])*base**j j+=1 except: j=0 l=tuple(l) print l return l print sorted(alist,key=key) 

    вывод:

     ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 1) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 2) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 7) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 2) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 3) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 4) ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 9) ('b', 'e', 't', 'a', 1, '.', 1) ('b', 'e', 't', 'a', 2, '.', 3, '.') ('b', 'e', 't', 'a', 2, '.', 30, 3, '.', 1) ('a', 1) ('a', 2) ('z', 2) ('z', 1) ['a001', 'a2', 'beta1.1', 'beta2.3.0', 'beta2.33.1', 'something1', 'something2', 'something12', 'something17', 'something25and_then_33', 'something25and_then_34', 'something29', 'z1', 'z002'] 

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

     def natural_sort_key(string_or_number): """ by Scott S. Lawton <scott@ProductArchitect.com> 2014-12-11; public domain and/or CC0 license handles cases where simple 'int' approach fails, eg ['0.501', '0.55'] floating point with different number of significant digits [0.01, 0.1, 1] already numeric so regex and other string functions won't work (and aren't required) ['elm1', 'Elm2'] ASCII vs. letters (not case sensitive) """ def try_float(astring): try: return float(astring) except: return astring if isinstance(string_or_number, basestring): string_or_number = string_or_number.lower() if len(re.findall('[.]\d', string_or_number)) <= 1: # assume a floating point value, eg to correctly sort ['0.501', '0.55'] # '.' for decimal is locale-specific, eg correct for the Anglosphere and Asia but not continental Europe return [try_float(s) for s in re.split(r'([\d.]+)', string_or_number)] else: # assume distinct fields, eg IP address, phone number with '.', etc. # caveat: might want to first split by whitespace # TBD: for unicode, replace isdigit with isdecimal return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_or_number)] else: # consider: add code to recurse for lists/tuples and perhaps other iterables return string_or_number 

    Тестовый код и несколько ссылок (вкл и выкл StackOverflow) находятся здесь: http://productarchitect.com/code/better-natural-sort.py

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

    Скорее всего functools.cmp_to_key() тесно связан с базовой реализацией вида python. Кроме того, параметр cmp является устаревшим. Современный способ состоит в том, чтобы преобразовать входные элементы в объекты, которые поддерживают желаемые операции сравнения.

    В CPython 2.x объекты разрозненных типов можно упорядочить, даже если соответствующие богатые операторы сравнения не были реализованы. В CPython 3.x объекты разных типов должны явно поддерживать сравнение. См. Как Python сравнивает строку и int? который связан с официальной документацией . Большинство ответов зависят от этого неявного упорядочения. Для переключения на Python 3.x потребуется новый тип для реализации и унификации сравнений между числами и строками.

     Python 2.7.12 (default, Sep 29 2016, 13:30:34) >>> (0,"foo") < ("foo",0) True 
     Python 3.5.2 (default, Oct 14 2016, 12:54:53) >>> (0,"foo") < ("foo",0) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unorderable types: int() < str() 

    Существует три разных подхода. Первый использует вложенные классы, чтобы воспользоваться алгоритмом сравнения Iterable Python. Второй разворачивает это вложение в один класс. Третий приоритет подклассифицирует str чтобы сосредоточиться на производительности. Все приурочены; второй – в два раза быстрее, а третий почти в шесть раз быстрее. Subclassing str не требуется, и, вероятно, это была плохая идея, но в ней есть определенные удобства.

    Символы сортировки дублируются для принудительного упорядочения в каждом конкретном случае и с заменой case, чтобы заставить строчную букву сначала сортировать; это типичное определение «естественного сорта». Я не мог определиться с типом группировки; некоторые из них могут предпочесть следующее, что также приносит значительные преимущества в плане производительности:

     d = lambda s: s.lower()+s.swapcase() 

    Где используется, операторы сравнения настроены на object поэтому они не будут игнорироваться functools.total_ordering .

     import functools import itertools @functools.total_ordering class NaturalStringA(str): def __repr__(self): return "{}({})".format\ ( type(self).__name__ , super().__repr__() ) d = lambda c, s: [ c.NaturalStringPart("".join(v)) for k,v in itertools.groupby(s, c.isdigit) ] d = classmethod(d) @functools.total_ordering class NaturalStringPart(str): d = lambda s: "".join(c.lower()+c.swapcase() for c in s) d = staticmethod(d) def __lt__(self, other): if not isinstance(self, type(other)): return NotImplemented try: return int(self) < int(other) except ValueError: if self.isdigit(): return True elif other.isdigit(): return False else: return self.d(self) < self.d(other) def __eq__(self, other): if not isinstance(self, type(other)): return NotImplemented try: return int(self) == int(other) except ValueError: if self.isdigit() or other.isdigit(): return False else: return self.d(self) == self.d(other) __le__ = object.__le__ __ne__ = object.__ne__ __gt__ = object.__gt__ __ge__ = object.__ge__ def __lt__(self, other): return self.d(self) < self.d(other) def __eq__(self, other): return self.d(self) == self.d(other) __le__ = object.__le__ __ne__ = object.__ne__ __gt__ = object.__gt__ __ge__ = object.__ge__ 
     import functools import itertools @functools.total_ordering class NaturalStringB(str): def __repr__(self): return "{}({})".format\ ( type(self).__name__ , super().__repr__() ) d = lambda s: "".join(c.lower()+c.swapcase() for c in s) d = staticmethod(d) def __lt__(self, other): if not isinstance(self, type(other)): return NotImplemented groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other)) zipped = itertools.zip_longest(*groups) for s,o in zipped: if s is None: return True if o is None: return False s_k, s_v = s[0], "".join(s[1]) o_k, o_v = o[0], "".join(o[1]) if s_k and o_k: s_v, o_v = int(s_v), int(o_v) if s_v == o_v: continue return s_v < o_v elif s_k: return True elif o_k: return False else: s_v, o_v = self.d(s_v), self.d(o_v) if s_v == o_v: continue return s_v < o_v return False def __eq__(self, other): if not isinstance(self, type(other)): return NotImplemented groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other)) zipped = itertools.zip_longest(*groups) for s,o in zipped: if s is None or o is None: return False s_k, s_v = s[0], "".join(s[1]) o_k, o_v = o[0], "".join(o[1]) if s_k and o_k: s_v, o_v = int(s_v), int(o_v) if s_v == o_v: continue return False elif s_k or o_k: return False else: s_v, o_v = self.d(s_v), self.d(o_v) if s_v == o_v: continue return False return True __le__ = object.__le__ __ne__ = object.__ne__ __gt__ = object.__gt__ __ge__ = object.__ge__ 
     import functools import itertools import enum class OrderingType(enum.Enum): PerWordSwapCase = lambda s: s.lower()+s.swapcase() PerCharacterSwapCase = lambda s: "".join(c.lower()+c.swapcase() for c in s) class NaturalOrdering: @classmethod def by(cls, ordering): def wrapper(string): return cls(string, ordering) return wrapper def __init__(self, string, ordering=OrderingType.PerCharacterSwapCase): self.string = string self.groups = [ (k,int("".join(v))) if k else (k,ordering("".join(v))) for k,v in itertools.groupby(string, str.isdigit) ] def __repr__(self): return "{}({})".format\ ( type(self).__name__ , self.string ) def __lesser(self, other, default): if not isinstance(self, type(other)): return NotImplemented for s,o in itertools.zip_longest(self.groups, other.groups): if s is None: return True if o is None: return False s_k, s_v = s o_k, o_v = o if s_k and o_k: if s_v == o_v: continue return s_v < o_v elif s_k: return True elif o_k: return False else: if s_v == o_v: continue return s_v < o_v return default def __lt__(self, other): return self.__lesser(other, default=False) def __le__(self, other): return self.__lesser(other, default=True) def __eq__(self, other): if not isinstance(self, type(other)): return NotImplemented for s,o in itertools.zip_longest(self.groups, other.groups): if s is None or o is None: return False s_k, s_v = s o_k, o_v = o if s_k and o_k: if s_v == o_v: continue return False elif s_k or o_k: return False else: if s_v == o_v: continue return False return True # functools.total_ordering doesn't create single-call wrappers if both # __le__ and __lt__ exist, so do it manually. def __gt__(self, other): op_result = self.__le__(other) if op_result is NotImplemented: return op_result return not op_result def __ge__(self, other): op_result = self.__lt__(other) if op_result is NotImplemented: return op_result return not op_result # __ne__ is the only implied ordering relationship, it automatically # delegates to __eq__ 
     >>> import natsort >>> import timeit >>> l1 = ['Apple', 'corn', 'apPlE', 'arbour', 'Corn', 'Banana', 'apple', 'banana'] >>> l2 = list(map(str, range(30))) >>> l3 = ["{} {}".format(x,y) for x in l1 for y in l2] >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringA)', number=10000, globals=globals())) 362.4729259099986 >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringB)', number=10000, globals=globals())) 189.7340817489967 >>> print(timeit.timeit('sorted(l3+["0"], key=NaturalOrdering.by(OrderingType.PerCharacterSwapCase))', number=10000, globals=globals())) 69.34636392899847 >>> print(timeit.timeit('natsort.natsorted(l3+["0"], alg=natsort.ns.GROUPLETTERS | natsort.ns.LOWERCASEFIRST)', number=10000, globals=globals())) 98.2531585780016 

    Естественная сортировка довольно сложна и неопределенно определена как проблема. Не забудьте запустить unicodedata.normalize(...) заранее и подумайте об использовании str.casefold() а не str.lower() . Вероятно, есть тонкие проблемы с кодированием, которые я не рассматривал. Поэтому я предварительно рекомендую библиотеку natsort . Я быстро взглянул на хранилище github; обслуживание кода было звездным.

    Все алгоритмы, которые я видел, зависят от трюков, таких как дублирование и опускание символов, а также замена футляра. Хотя это удваивает время работы, альтернатива потребует полного естественного упорядочения на наборе символов ввода. Я не думаю, что это часть спецификации юникода, и поскольку число символов в формате unicode намного меньше [0-9] , создание такой сортировки будет одинаково сложным. Если вы хотите сравнить локали, подготовьте свои строки с помощью locale.strxfrm для сортировки Python КАК .

    Я предлагаю вам просто использовать аргумент ключевого key слова, sorted для достижения нужного вам списка
    Например:

     to_order= [e2,E1,e5,E4,e3] ordered= sorted(to_order, key= lambda x: x.lower()) # ordered should be [E1,e2,e3,E4,e5] 
     >>> import re >>> sorted(lst, key=lambda x: int(re.findall(r'\d+$', x)[0])) ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'] 
    Python - лучший язык программирования в мире.