스트링 내추럴 소트 기능이 내장되어 있나요?
자연스러운 알파벳 정렬을 하고 싶은 문자열 목록이 있습니다.
예를 들어 다음 목록은 자연스럽게 정렬됩니다(원하는 항목).
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
위 목록의 "정렬된" 버전은 다음과 같습니다(사용하는 항목).
['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
처음처럼 동작하는 소트 기능을 찾고 있습니다.
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']
업데이트 2020년 11월
인 요구은 " 브라우저가 인 "Windows 탐색기처럼 정렬하는 방법"(Windows 탐색기 파일 시스템 브라우저)입니다.natsort버전 7.1.0에는 정확하게 이를 수행하기 위한 기능이 있습니다.Windows 에서는 Windows 탐색기와 같은 순서로 정렬되며, 다른 운영 체제에서는 로컬 파일 시스템 브라우저와 같이 정렬됩니다.
>>> from natsort import os_sorted
>>> os_sorted(list_of_paths)
# your paths sorted like your file system browser
키가 은 '소트 키'를 .os_sort_keygen (오류)os_sort_key★★★★★★★★★★★★★★★★★★★★★★★★★」
경고 - 를 사용하기 전에 이 함수에 대한 API 문서를 읽고 제한 사항과 최상의 결과를 얻는 방법을 이해하십시오.
이것을 시험해 보세요.
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']
여기서부터의 코드는, 「사람을 위한 정렬」: 「Natural Sort Order」를 채용했습니다.
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)]
완전히 재현 가능한 데모 코드:
import re
natsort = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]
L = ["a1", "a10", "a11", "a2", "a22", "a3"]
print(sorted(L, key=natsort))
# ['a1', 'a2', 'a3', 'a10', 'a11', 'a22']
data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10']
데이터를 분석합시다.2번 공통 세 가 있어요.'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:]) 뒤의 문자열 부분을 정수로 변환합니다.데이터의 숫자 부분이 다른 위치에 있으면 함수의 이 부분을 변경해야 합니다.
저는 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'}]
그리고 좀 더 우아한*을 위해 (피토닉)
·조금만 만져도
많은 구현이 이루어지고 있지만, 현대 비단뱀이 제공하는 우아함을 제대로 포착한 것은 없습니다.
- python(3.5.1)을 사용하여 테스트 완료
- 숫자가 중간 문자열일 때 작동한다는 것을 보여주기 위해 추가 목록을 포함했습니다.
- 테스트하지 않았습니다만, 만약 당신의 리스트가 크다면, 사전에 regex를 컴파일하는 것이 더 효율적이라고 생각합니다.
- 만약 이것이 잘못된 가정이라면 누군가가 나를 바로잡아 줄 것이라고 확신한다.
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 문서 - 방법 정렬
- 인간 정렬: 자연 정렬 순서
- 휴먼 정렬
- 이에 대한 기여자/댓글 및 참조 게시물
이 투고의 가치
제 요점은 일반적으로 적용할 수 있는 비 regex 솔루션을 제공하는 것입니다.
을 하다
find_first_digit@AnuragUnial에서 빌렸습니다.문자열에서 첫 번째 자리 또는 숫자 이외의 위치를 찾습니다.split_digits이것은 문자열을 숫자 청크와 숫자 이외의 청크로 분리하는 제너레이터입니다., ,,yield정수(숫자일 경우)를 지정합니다.natural_keysplit_digitstuple''로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))
하나의 옵션은 문자열을 태플로 변환하고 확장형식 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']
Claudiu의 Mark Byers 답변 개선;-)
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)
이 '하다'에서 평가되는 수 있습니다.def
답을 는 '아까운 친구'라고 썼어요.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에서도 이용할 수 있습니다.
하는 알고리즘은 '알고리즘'입니다.padzero_with_lower뭇매를 맞다
import re
def padzero_with_lower(s):
return re.sub(r'\d+', lambda m: m.group(0).rjust(10, '0'), s).lower()
알고리즘은 다음을 찾습니다.
- 예를 들어 10과 같이 충분한 길이로 임의의 길이의 패드를 찾아 패드를 붙인다.
- 그런 다음 문자열을 소문자로 변환합니다.
다음은 사용 예를 제시하겠습니다.
print(padzero_with_lower('file1.txt')) # file0000000001.txt
print(padzero_with_lower('file12.txt')) # file0000000012.txt
print(padzero_with_lower('file23.txt')) # file0000000023.txt
print(padzero_with_lower('file123.txt')) # file0000000123.txt
print(padzero_with_lower('file301.txt')) # file0000000301.txt
print(padzero_with_lower('Dir2/file15.txt')) # dir0000000002/file0000000015.txt
print(padzero_with_lower('dir2/file123.txt')) # dir0000000002/file0000000123.txt
print(padzero_with_lower('dir15/file2.txt')) # dir0000000015/file0000000002.txt
print(padzero_with_lower('Dir15/file15.txt')) # dir0000000015/file0000000015.txt
print(padzero_with_lower('elm0')) # elm0000000000
print(padzero_with_lower('elm1')) # elm0000000001
print(padzero_with_lower('Elm2')) # elm0000000002
print(padzero_with_lower('elm9')) # elm0000000009
print(padzero_with_lower('elm10')) # elm0000000010
print(padzero_with_lower('Elm11')) # elm0000000011
print(padzero_with_lower('Elm12')) # elm0000000012
print(padzero_with_lower('elm13')) # elm0000000013
이 기능을 테스트하면, 이것을 열쇠로서 사용할 수 있습니다.
lis = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
lis.sort(key=padzero_with_lower)
print(lis)
# Output: ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
a a로 List[Tuple(str, int)].
코드
def string_to_pairs(s, pairs=re.compile(r"(\D*)(\d*)").findall):
return [(text.lower(), int(digits or 0)) for (text, digits) in pairs(s)[:-1]]
데모
sorted(['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9'], key=string_to_pairs)
출력:
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
테스트
변혁
assert string_to_pairs("") == []
assert string_to_pairs("123") == [("", 123)]
assert string_to_pairs("abc") == [("abc", 0)]
assert string_to_pairs("123abc") == [("", 123), ("abc", 0)]
assert string_to_pairs("abc123") == [("abc", 123)]
assert string_to_pairs("123abc456") == [("", 123), ("abc", 456)]
assert string_to_pairs("abc123efg") == [("abc", 123), ("efg", 0)]
정렬
# Some extracts from the test suite of the natsort library. Permalink:
# https://github.com/SethMMorton/natsort/blob/e3c32f5638bf3a0e9a23633495269bea0e75d379/tests/test_natsorted.py
sort_data = [
( # same as test_natsorted_can_sort_as_unsigned_ints_which_is_default()
["a50", "a51.", "a50.31", "a-50", "a50.4", "a5.034e1", "a50.300"],
["a5.034e1", "a50", "a50.4", "a50.31", "a50.300", "a51.", "a-50"],
),
( # same as test_natsorted_numbers_in_ascending_order()
["a2", "a5", "a9", "a1", "a4", "a10", "a6"],
["a1", "a2", "a4", "a5", "a6", "a9", "a10"],
),
( # same as test_natsorted_can_sort_as_version_numbers()
["1.9.9a", "1.11", "1.9.9b", "1.11.4", "1.10.1"],
["1.9.9a", "1.9.9b", "1.10.1", "1.11", "1.11.4"],
),
( # different from test_natsorted_handles_filesystem_paths()
[
"/p/Folder (10)/file.tar.gz",
"/p/Folder (1)/file (1).tar.gz",
"/p/Folder/file.x1.9.tar.gz",
"/p/Folder (1)/file.tar.gz",
"/p/Folder/file.x1.10.tar.gz",
],
[
"/p/Folder (1)/file (1).tar.gz",
"/p/Folder (1)/file.tar.gz",
"/p/Folder (10)/file.tar.gz",
"/p/Folder/file.x1.9.tar.gz",
"/p/Folder/file.x1.10.tar.gz",
],
),
( # same as test_natsorted_path_extensions_heuristic()
[
"Try.Me.Bug - 09 - One.Two.Three.[text].mkv",
"Try.Me.Bug - 07 - One.Two.5.[text].mkv",
"Try.Me.Bug - 08 - One.Two.Three[text].mkv",
],
[
"Try.Me.Bug - 07 - One.Two.5.[text].mkv",
"Try.Me.Bug - 08 - One.Two.Three[text].mkv",
"Try.Me.Bug - 09 - One.Two.Three.[text].mkv",
],
),
( # same as ns.IGNORECASE for test_natsorted_supports_case_handling()
["Apple", "corn", "Corn", "Banana", "apple", "banana"],
["Apple", "apple", "Banana", "banana", "corn", "Corn"],
),
]
for (given, expected) in sort_data:
assert sorted(given, key=string_to_pairs) == expected
보너스
와 숫자가 혼재되어 있는 는, ASCII 의 작성에 경우가 .string_to_pairs()「」을 remove_diacritics()다른 데로 주세요.
위의 답변은 제시된 구체적인 예에서는 유용하지만, 자연에 관한 보다 일반적인 질문에서는 몇 가지 유용한 사례를 놓치고 있습니다.이러한 케이스 중 하나를 방금 입수했기 때문에 보다 철저한 솔루션을 작성했습니다.
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
피드백을 환영합니다.이것은 최종적인 솔루션이 아닙니다.한 걸음 앞으로 나아가면 됩니다.
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()
세 가지 접근법이 있습니다. 번째는 의 Python을 합니다.Iterable비교 알고리즘.두 번째는 이 네스트를 단일 클래스로 전개합니다.세 번째 서브 분류는 생략됩니다.str이치노두 번째는 2배 빠른 반면 세 번째는 거의 6배 빠른 타이밍입니다. ★★★★★str필수는 아니고, 애초에 나쁜 생각이었을지도 모르지만, 확실히 편리함이 수반됩니다.
정렬 문자는 대소문자를 기준으로 정렬하기 위해 복제되고 소문자를 먼저 정렬하기 위해 대소문자를 바꿉니다. 이것이 "자연 정렬"의 일반적인 정의입니다.그룹화 유형을 결정하지 못했습니다. 일부는 다음을 선호할 수 있으며, 이는 상당한 성능 이점을 가져옵니다.
d = lambda s: s.lower()+s.swapcase()
는 용용 are음음음음음음음음음음음음음음음음 are are are are are are are are are are are are are 로 설정됩니다.object에 의해 무시당하지 않도록 하기 위해서.
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()아마 제가 고려하지 않은 미묘한 부호화 문제가 있을 겁니다.그래서 나는 잠정적으로 낫소트 라이브러리를 추천한다.Github 저장소를 잠깐 살펴보니 코드 유지보수가 훌륭했습니다.
지금까지 본 알고리즘은 모두 문자 복제 및 감소, 대소문자 교환 등의 트릭에 의존합니다.이렇게 하면 실행 시간이 두 배로 늘어나지만, 다른 방법으로는 입력 문자 집합에서 완전히 자연스러운 순서가 필요합니다.가 '유니코드'보다 입니다.유니코드 숫자가 더 많기 때문에[0-9]이러한 분류를 작성하는 것도 마찬가지로 부담스러울 것입니다.로케일 인식 비교가 필요한 경우 Python의 Sorting HOW TO에 따라 문자열을 준비합니다.
은 @답변에 이어 @Mark Byers의 @Mark Byers의 @Mark Byers의 @Mark Byers의 @Mark Byers의 @Mark Byers의 @Mark Byers를 각색입니다.keyPEP8을 사용하다
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)
나도 Gist를 만들었어
이 요구에 대한 저만의 의견을 제출하겠습니다.
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=자연스러운 정렬을 수행합니다.
>>> sorted(filelist, key=grouper)
['File1', 'File3', 'File3a', 'File4', 'File5', 'File6', 'File007', 'File8',
'File8b1', 'File8b2', 'File8b11', 'File9', 'File10', 'File11']
단점은 물론 현재와 같이 함수는 대문자를 소문자보다 먼저 정렬한다는 것입니다.
대소문자를 구분하는 그루퍼의 실장은 독자에게 맡기겠습니다:-)
의 간단한 의 또 다른 솔루션에서는, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」, 「」의 호출을 하고 있습니다. 월터 트로스이러한 솔루션은 전화를 회피합니다.isdigit() 더 빨라질 수 있기 때문에 발생할 수 를 피할 수 isdigit() 유니코드 합니다.\d+.
import re
from itertools import cycle
_re_digits = re.compile(r"(\d+)")
def natural_comparison_key(key):
return tuple(
int(part) if is_digit else part
for part, is_digit in zip(_re_digits.split(key), cycle((False, True)))
)
여기 Mark Byers의 답변의 다른 버전이 있습니다.이 버전에서는 목록 내의 개체를 평가하는 데 사용되는 속성 이름을 전달하는 방법을 보여 줍니다.
def natural_sort(l, attrib):
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.__dict__[attrib])]
return sorted(l, key=alphanum_key)
results = natural_sort(albums, 'albumid')
서 ★★★★★albums는 앨범 입니다.albumid는 명목상 숫자가 포함된 문자열 속성입니다.
.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]
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
확인 응답:
>>> import re
>>> sorted(lst, key=lambda x: int(re.findall(r'\d+$', x)[0]))
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
언급URL : https://stackoverflow.com/questions/4836710/is-there-a-built-in-function-for-string-natural-sort
'programing' 카테고리의 다른 글
| 도커 컨테이너 실행 후 mysql 데이터 소유권이 systemd-journal-remote로 변경된 이유 (0) | 2022.11.04 |
|---|---|
| 데이터베이스 스키마를 어떻게 버전화합니까? (0) | 2022.11.04 |
| 함수를 호출할 때 "throws Exception"이 필요한 이유는 무엇입니까? (0) | 2022.11.04 |
| Larabel 알 수 없는 열 'updated_at' (0) | 2022.11.04 |
| PHP에서 try...catch 블록을 효율적으로 사용하는 방법 (0) | 2022.11.04 |