문자열 자연 정렬을위한 내장 함수가 있습니까?


281

Python 3.x를 사용하면 자연스러운 알파벳 정렬을 수행하려는 문자열 목록이 있습니다.

자연 정렬 : Windows의 파일이 정렬되는 순서입니다.

예를 들어 다음 목록은 자연스럽게 정렬됩니다 (원하는 것).

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

그리고 여기 위 목록의 "정렬 된"버전이 있습니다 :

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

첫 번째 기능처럼 작동하는 정렬 기능을 찾고 있습니다.


13
자연 정렬의 정의는 "Windows가 파일을 정렬하는 순서"가 아닙니다.
Glenn Maynard


예를 들어 sorting 과 같은 몇 가지 경우에 'Windows-Explorer-like' 정렬 을 원하면 이 사이트의 모든 답변에서 잘못된 결과 가 생성됩니다!1, 1, !a, a . Windows와 같은 정렬을 얻는 유일한 방법은 Windows StrCmpLogicalW 기능 자체 를 사용하는 것 같습니다. 아무도이 기능을 올바르게 다시 구현하지 않은 것 같습니다 (소스는 감사하겠습니다). 솔루션 : stackoverflow.com/a/48030307/2441026
user136036

답변:


235

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설명서의 작동 방식 페이지, 특히 특별 사례를 확인하십시오! 부분.


정렬 함수 대신 정렬 키가 필요한 경우 아래 수식 중 하나를 사용하십시오.

>>> 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']

5
또한 natsort가 숫자가 끝날 때 올바르게 정렬되는 것도 흥미 롭습니다. 종종 파일 이름의 경우와 같습니다. 다음 예를 포함 자유롭게 pastebin.com/9cwCLdEK
마틴 토마스

1
Natsort는 훌륭한 라이브러리이므로 파이썬 표준 라이브러리에 추가해야합니다! :-)
Mitch McMabers

natsort또한 '자연스럽게'문자열에서 여러 개의 개별 숫자의 경우를 처리합니다. 좋은 물건!
FlorianH

182

이 시도:

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']

여기에서 적용된 코드 : 인간을위한 정렬 : 자연 정렬 순서 .


2
return sorted(l, key)대신에 사용 l.sort(key)합니까? 성능 향상을위한 것입니까, 아니면 더 파이썬적인 것입니까?
jperelli

12
@jperelli 나는 사다리가 발신자의 원래 목록을 바꿀 것이라고 생각합니다. 그러나 발신자는 목록의 다른 얕은 사본을 원할 것입니다.
huggie

3
레코드의 경우 모든 입력을 처리 할 수는 없습니다. str / int 분할이 정렬되어야합니다. 그렇지 않으면 입력 [ "foo0에 대해 ["foo ", 0] <[0,"foo "]와 같은 비교를 작성합니다. ","0foo "]는 TypeError를 발생시킵니다.
user19087

4
@ user19087 : 실제로 re.split('([0-9]+)', '0foo')반환합니다 ['', '0', 'foo']. 이 때문에 문자열은 배열의 홀수 인덱스에서 항상 짝수 인덱스와 정수에있게됩니다.
Florian Kusche

성능에 대해 궁금한 사람은 파이썬의 기본 정렬보다 느립니다. 즉, 25-50 배 느려집니다. 그리고 항상 [elm1, elm2, Elm2, elm2]를 [elm1, Elm2, elm2, elm2]로 안정적으로 정렬하려면 (먼저 대문자), 간단히 natural_sort (sorted (lst))를 호출하면됩니다. 더 비효율적이지만 반복 가능한 정렬을 얻는 것이 매우 쉽습니다. ~ 50 % 속도 향상을 위해 정규식을 컴파일하십시오. Claudiu의 답변에서 볼 수 있듯이.
찰리 헤일리

100

Mark Byer의 답변은 훨씬 더 파이썬 버전입니다.

import re

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

이제이 기능을 사용하는 기능, 등의 키로서 사용할 수있다 list.sort, sorted, max

람다로서 :

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

9
re 모듈은 자동적으로 정규 표현식을 컴파일하고 캐싱하므로 미리 컴파일 할 필요가 없습니다
wim

1
@wim : 마지막 X 사용을 캐시하므로 기술적으로 X + 5 정규 표현식을 사용한 다음 자연스럽게 정렬하는 것이 가능합니다.이 시점에서 캐시되지 않습니다. 하지만 장기적으로 아마 무시할
Claudiu

나는 그것을하지 않았지만 아마도 그 이유는 일반적인 파이썬 정렬과 같이 튜플을 처리 할 수 ​​없었을 것입니다.
Unfun Cat

1
@Claudiu가 언급 한 X 사용법 은 Python 2.7에서 100 , Python 3.4에서 512로 보입니다 . 또한 한도에 도달하면 캐시가 완전히 지워집니다 (따라서 가장 오래된 것만이 아님).
Zitrax

@Zitrax 왜 / 캐시를 완전히 지우는 것이 이치에 맞습니까?
Joschua

19

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'}]

이 작업을 수행하는 가장 간단한 방법은을 정의하는 것입니다. natural_sort_key그런 다음 목록을 정렬 할 때 다음과 같이 키를 연결할 수 있습니다.list.sort(key=lambda el: natural_sort_key(el['name']))
Claudiu

17
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

1
동적 / 알 수없는 길이의 데이터는 처리하지 않습니다. 또한 마지막에 반대되는 데이터 내에 숫자가있는 데이터에 대한 다른 솔루션과 다르게 정렬됩니다. * 반드시 바람직하지는 않지만 지적하는 것이 좋습니다.
JerodG

1
동적 길이 데이터를 처리해야하는 경우 위 내용 width = max(data, key=len)에 대해 무엇을 계산 8하고 다음을 사용하여 형식 문자열로 하위 데이터 를 계산하는 데 사용할 수 있습니다.'{0:0>{width}}'.format(x, width=width)
roganartu

1
이 솔루션은이 포럼의 다른 모든 솔루션과 비교하여 시간이 정해진 테스트를 수행함으로써 @snakile이 처리하려고하는 데이터 유형에 가장 빠르고 효율적입니다
SR Colledge

13

주어진:

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 :])). 데이터의 숫자 부분이 다른 위치에 있으면 함수의이 부분을 변경해야합니다.

건배


6
그리고 이제 더 * 우아한 (pythonic) 무언가를 위해 -터치

많은 구현이 있으며 일부는 가까이 왔지만 현대 파이썬이 제공하는 우아함을 포착하지 못했습니다.

  • 파이썬 (3.5.1)을 사용하여 테스트
  • 숫자가 중간 문자열 일 때 작동한다는 것을 보여주는 추가 목록이 포함되었습니다.
  • 그러나 테스트하지는 않았지만 목록의 크기가 조정 가능하면 미리 정규식을 컴파일하는 것이 더 효율적이라고 가정합니다
    • 이것이 잘못된 가정이라면 누군가 나를 교정 할 것이라고 확신합니다.

빨리
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
    • 수입품을 차별화해야합니다

영감 에서


6

이 게시물의 가치

내 요점은 일반적으로 적용 할 수있는 비 정규식 솔루션을 제공하는 것입니다.
세 가지 함수를 만들겠습니다.

  1. find_first_digit@AnuragUniyal 에서 빌린 것 입니다 . 문자열에서 첫 번째 숫자 또는 숫자가 아닌 위치를 찾습니다.
  2. split_digits이것은 문자열을 숫자와 비 숫자 청크로 분리하는 생성기입니다. yield숫자 일 때도 정수가됩니다.
  3. natural_key그냥에 래핑 split_digits합니다 tuple. 이것은 우리의 핵심으로 사용하는 것입니다 sorted, max, min.

기능

def find_first_digit(s, non=False):
    for i, x in enumerate(s):
        if x.isdigit() ^ non:
            return i
    return -1

def split_digits(s, case=False):
    non = True
    while s:
        i = find_first_digit(s, non)
        if i == 0:
            non = not non
        elif i == -1:
            yield int(s) if s.isdigit() else s if case else s.lower()
            s = ''
        else:
            x, s = s[:i], s[i:]
            yield int(x) if x.isdigit() else x if case else x.lower()

def natural_key(s, *args, **kwargs):
    return tuple(split_digits(s, *args, **kwargs))

우리는 여러 자리 청크를 가질 수 있다는 것이 일반적이라는 것을 알 수 있습니다.

# Note that the key has lower case letters
natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh')

('asl;dkfdfkj:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

또는 대소 문자를 구분하여 그대로 두십시오.

natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh', True)

('asl;dkfDFKJ:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

OP의 목록이 적절한 순서로 정렬되어 있음을 알 수 있습니다

sorted(
    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'],
    key=natural_key
)

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

그러나 더 복잡한 목록도 처리 할 수 ​​있습니다.

sorted(
    ['f_1', 'e_1', 'a_2', 'g_0', 'd_0_12:2', 'd_0_1_:2'],
    key=natural_key
)

['a_2', 'd_0_1_:2', 'd_0_12:2', 'e_1', 'f_1', 'g_0']

내 정규식은

def int_maybe(x):
    return int(x) if str(x).isdigit() else x

def split_digits_re(s, case=False):
    parts = re.findall('\d+|\D+', s)
    if not case:
        return map(int_maybe, (x.lower() for x in parts))
    else:
        return map(int_maybe, parts)
    
def natural_key_re(s, *args, **kwargs):
    return tuple(split_digits_re(s, *args, **kwargs))

1
고마워요! 그러나 "12345_A"와 "12345_A2"가 있으면 후자가 첫 번째보다 먼저 정렬됩니다. 이것은 적어도 Windows 가하는 방식이 아닙니다. 그래도 여전히 위의 문제에 대해 작동합니다!
morph3us

4

한 가지 옵션은 문자열을 튜플로 바꾸고 확장 양식 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']

1
불행히도이 솔루션은 Python 2.X에서만 작동합니다. 파이썬 3의 경우, ('b', 1) < ('b', 'e', 't', 'a', 1, '.', 1)반환TypeError: unorderable types: int() < str()
SethMMorton

@SethMMorgon이 코드는 쉽게 파이썬 3의 휴식은 자연의 대안이 보인다, 권리 natsort, pypi.org/project/natsort
FlorianH

3

여기에 대한 답변을 바탕으로 natural_sorted내장 함수처럼 동작 하는 함수를 작성했습니다 sorted.

# Copyright (C) 2018, Benjamin Drung <bdrung@posteo.de>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import re

def natural_sorted(iterable, key=None, reverse=False):
    """Return a new naturally sorted list from the items in *iterable*.

    The returned list is in natural sort order. The string is ordered
    lexicographically (using the Unicode code point number to order individual
    characters), except that multi-digit numbers are ordered as a single
    character.

    Has two optional arguments which must be specified as keyword arguments.

    *key* specifies a function of one argument that is used to extract a
    comparison key from each list element: ``key=str.lower``.  The default value
    is ``None`` (compare the elements directly).

    *reverse* is a boolean value.  If set to ``True``, then the list elements are
    sorted as if each comparison were reversed.

    The :func:`natural_sorted` function is guaranteed to be stable. A sort is
    stable if it guarantees not to change the relative order of elements that
    compare equal --- this is helpful for sorting in multiple passes (for
    example, sort by department, then by salary grade).
    """
    prog = re.compile(r"(\d+)")

    def alphanum_key(element):
        """Split given key in list of strings and digits"""
        return [int(c) if c.isdigit() else c for c in prog.split(key(element)
                if key else element)]

    return sorted(iterable, key=alphanum_key, reverse=reverse)

소스 코드는 내 GitHub 스 니펫 저장소에서도 사용할 수 있습니다 : https://github.com/bdrung/snippets/blob/master/natural_sorted.py


2

위의 답변은 표시된 특정 예제 에는 좋지만 자연적인 정렬에 대한 일반적인 질문에는 몇 가지 유용한 사례가 누락되었습니다. 나는 그중 하나의 사례에 조금만 빠져서 더 철저한 해결책을 만들었습니다.

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, e.g.
        ['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, e.g. to correctly sort ['0.501', '0.55']
            # '.' for decimal is locale-specific, e.g. 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, e.g. 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

피드백 환영합니다. 결정적인 해결책이 아닙니다. 한 걸음 앞으로 나아갑니다.


연결하는 테스트 스크립트에서 잘못 사용 natsorted되어 humansorted실패했습니다 ... natsorted키로 전달하려고 했지만 실제로 정렬 기능 자체 를 전달하려고했습니다 . 당신은 시도해야합니다 natsort_keygen().
SethMMorton

2

대부분 functools.cmp_to_key()은 파이썬 정렬의 기본 구현과 밀접한 관련이 있습니다. 또한 cmp 매개 변수는 레거시입니다. 현대적인 방법은 입력 항목을 원하는 풍부한 비교 연산을 지원하는 객체로 변환하는 것입니다.

CPython 2.x에서는 각각의 풍부한 비교 연산자가 구현되지 않은 경우에도 서로 다른 유형의 객체를 정렬 할 수 있습니다. CPython 3.x에서 다른 유형의 오브젝트는 명시 적으로 비교를 지원해야합니다. 파이썬은 문자열과 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()

세 가지 접근 방식이 있습니다. 첫 번째는 중첩 클래스를 사용하여 Python의 Iterable비교 알고리즘 을 활용 합니다. 두 번째는이 중첩을 단일 클래스로 전개합니다. 세 번째 str는 성능에 중점을 둔 서브 클래 싱 을 포기 합니다. 모든 시간이 정해져있다. 두 번째는 두 배 빠르지 만 세 번째는 거의 6 배 빠릅니다. 서브 클래 싱 str은 필요하지 않으며 처음에는 좋지 않은 아이디어 였지만 특정 편의가 제공됩니다.

정렬 문자는 대소 문자별로 순서를 정하기 위해 복제되며, 소문자를 먼저 정렬하도록 대소 문자를 바꿉니다. 이것이 "자연적 분류"의 전형적인 정의입니다. 그룹화 유형을 결정할 수 없습니다. 일부는 다음을 선호 할 수 있으며 이로 인해 상당한 성능 이점이 있습니다.

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 저장소를 한 눈에 살펴 보았습니다. 코드 유지 보수는 훌륭했습니다.

내가 본 모든 알고리즘은 문자 복제 및 축소 및 대소 문자 바꾸기와 같은 트릭에 달려 있습니다. 이것은 실행 시간을 두 배로 늘리는 반면, 대안은 입력 문자 세트에 대한 전체적인 자연 순서를 요구합니다. 나는 이것이 유니 코드 사양의 일부라고 생각하지 않으며,보다 많은 유니 코드 숫자가 있기 때문에 [0-9]그러한 정렬을 만드는 것은 똑같이 어려울 것입니다. 로케일 인식 비교를 원하면 locale.strxfrmPython의 Sorting HOW TO에 따라 문자열을 준비하십시오 .


1

이 필요에 대해 본인의 의견을 제출하겠습니다.

from typing import Tuple, Union, Optional, Generator


StrOrInt = Union[str, int]


# On Python 3.6, string concatenation is REALLY fast
# Tested myself, and this fella also tested:
# https://blog.ganssle.io/articles/2019/11/string-concat.html
def griter(s: str) -> Generator[StrOrInt, None, None]:
    last_was_digit: Optional[bool] = None
    cluster: str = ""
    for c in s:
        if last_was_digit is None:
            last_was_digit = c.isdigit()
            cluster += c
            continue
        if c.isdigit() != last_was_digit:
            if last_was_digit:
                yield int(cluster)
            else:
                yield cluster
            last_was_digit = c.isdigit()
            cluster = ""
        cluster += c
    if last_was_digit:
        yield int(cluster)
    else:
        yield cluster
    return


def grouper(s: str) -> Tuple[StrOrInt, ...]:
    return tuple(griter(s))

이제 다음과 같은 목록이 있다면 :

filelist = [
    'File3', 'File007', 'File3a', 'File10', 'File11', 'File1', 'File4', 'File5',
    'File9', 'File8', 'File8b1', 'File8b2', 'File8b11', 'File6'
]

key=kwarg를 사용하여 자연스럽게 정렬 할 수 있습니다 .

>>> sorted(filelist, key=grouper)
['File1', 'File3', 'File3a', 'File4', 'File5', 'File6', 'File007', 'File8', 
'File8b1', 'File8b2', 'File8b11', 'File9', 'File10', 'File11']

여기서 단점은 물론 현재와 마찬가지로 함수는 대문자를 소문자보다 먼저 정렬합니다.

대소 문자 구분 그룹화 구현을 독자에게 맡길 것입니다 :-)


0

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]

1
이것은 숫자를 처리하지 않습니다. a_51이후 것이다 a500500> 51 비록
skjerns

사실, 내 대답은 주어진 Elm11 및 elm1의 예와 단순히 일치합니다. 자연 정렬에 대한 요청을 구체적으로
놓쳤

0

@Mark Byers의 답변에 따라 다음은 key매개 변수 를 수용하고 PEP8을보다 잘 준수 하는 적응입니다 .

def natsorted(seq, key=None):
    def convert(text):
        return int(text) if text.isdigit() else text

    def alphanum(obj):
        if key is not None:
            return [convert(c) for c in re.split(r'([0-9]+)', key(obj))]
        return [convert(c) for c in re.split(r'([0-9]+)', obj)]

    return sorted(seq, key=alphanum)

나는 또한 요점을 만들었다


(-1)이 답변은 Mark와 비교하여 새로운 것을 가져 오지 않습니다 (어떤 린터도 PEP8- 일부 코드를 수정할 수 있습니다). 아니면 key매개 변수입니까? 그러나 이것은 또한 @beauburrier의 답변에 예시되어 있습니다
Ciprian Tomoiagă

0

Mark Byer의 답변에 대한 Claudiu의 개선에 대한 개선 ;-)

import re

def natural_sort_key(s, _re=re.compile(r'(\d+)')):
    return [int(t) if i & 1 else t.lower() for i, t in enumerate(_re.split(s))]

...
my_naturally_sorted_list = sorted(my_list, key=natural_sort_key)

BTW, 아마도 모든 사람들이 함수 인수 기본값이 def시간에 평가된다는 것을 기억하지는 않습니다.


-1
a = ['H1', 'H100', 'H10', 'H3', 'H2', 'H6', 'H11', 'H50', 'H5', 'H99', 'H8']
b = ''
c = []

def bubble(bad_list):#bubble sort method
        length = len(bad_list) - 1
        sorted = False

        while not sorted:
                sorted = True
                for i in range(length):
                        if bad_list[i] > bad_list[i+1]:
                                sorted = False
                                bad_list[i], bad_list[i+1] = bad_list[i+1], bad_list[i] #sort the integer list 
                                a[i], a[i+1] = a[i+1], a[i] #sort the main list based on the integer list index value

for a_string in a: #extract the number in the string character by character
        for letter in a_string:
                if letter.isdigit():
                        #print letter
                        b += letter
        c.append(b)
        b = ''

print 'Before sorting....'
print a
c = map(int, c) #converting string list into number list
print c
bubble(c)

print 'After sorting....'
print c
print a

감사의 글 :

버블 정렬 숙제

파이썬에서 한 번에 한 문자 씩 문자열을 읽는 방법


-2
>>> import re
>>> sorted(lst, key=lambda x: int(re.findall(r'\d+$', x)[0]))
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

4
구현은 숫자 문제 만 해결합니다. 문자열에 숫자가 없으면 구현이 실패합니다. 예를 들어 [ 'silent', 'ghost']에서 시도하십시오 (목록 색인이 범위를 벗어남).
snakile

2
@ snaklie : 귀하의 질문에 적절한 사례가 제공되지 않습니다. 무엇을 하려는지 설명하지 않았으며이 새로운 정보로 질문을 업데이트하지도 않았습니다. 시도한 내용을 게시하지 않았으므로 내 텔레파시 시도를 너무 무시하지 마십시오.
SilentGhost

5
@SilentGhost : 첫째, 나는 당신의 대답이 유용하다고 생각하기 때문에 공감대를주었습니다. 둘째, 가능한 모든 사례를 예제로 다룰 수는 없습니다. 나는 자연적인 분류에 대해 아주 명확한 정의를했다고 생각합니다. 나는 그런 단순한 개념에 대한 복잡한 예나 긴 정의를 제시하는 것이 좋지 않다고 생각합니다. 문제에 대한 더 나은 공식화를 생각할 수 있다면 내 질문을 편집 할 수 있습니다.
snakile

1
@SilentGhost : Windows가 파일을 이름별로 정렬 할 때 (사례 무시 등) Windows가 파일 이름을 처리하는 것과 같은 방식으로 이러한 문자열을 처리하고 싶습니다. 그것은 나에게는 분명해 보이지만, 내가 말하는 것은 나에게 분명해 보이기 때문에 그것이 분명한지 판단하지는 않습니다.
snakile

1
@ snakile 당신은 자연 검색을 정의하는 데 가까워졌습니다. 그것은 매우 어려울 것이고 많은 세부 사항이 필요할 것입니다. Windows 탐색기에서 사용되는 정렬 순서를 원한다면 이것을 제공하는 간단한 API 호출이 있다는 것을 알고 있습니까?
David Heffernan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.