Doğal sıralama dizisi için yerleşik bir işlev var mı?


281

Python 3.x kullanarak, doğal alfabetik sıralaması yapmak istediğiniz dizelerin bir listesi var.

Doğal sıralama: Windows'daki dosyaların sıralanma sırası.

Örneğin, aşağıdaki liste doğal olarak sıralanmıştır (istediğim):

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

Ve işte yukarıdaki listenin "sıralı" sürümü (sahip olduğum):

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

Birincisi gibi davranan bir sıralama işlevi arıyorum.


13
Doğal bir türün tanımı "Windows'un dosyaları sıraladığı sıra" değildir.
Glenn Maynard


Örneğin, sıralama gibi 'Windows-Explorer benzeri' sıralama yapmak istiyorsanız , bu sitedeki tüm yanıtlar yanlış sonuçlar verir !1, 1, !a, a. Windows gibi sıralama yapmanın tek yolu, Windows StrCmpLogicalW işlevinin kendisini kullanmak gibi görünüyor, çünkü kimse bu işlevi doğru bir şekilde yeniden uygulamıyor gibi görünüyor (kaynak takdir edilecektir). Çözüm: stackoverflow.com/a/48030307/2441026
user136036

Yanıtlar:


235

Bunun için PyPI'de natsort adlı bir üçüncü taraf kütüphanesi var (tam açıklama, paketin yazarıyım ). Durumunuz için aşağıdakilerden birini yapabilirsiniz:

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

natsortGenel bir algoritma kullandığını ve bu nedenle ona attığınız herhangi bir girdi için çalışması gerektiğini unutmayın . Kendi işlevinizi yerine getirmek yerine neden bunu yapmak için bir kütüphane seçebileceğinizle ilgili daha fazla ayrıntı istiyorsanız, natsortbelgelerin Nasıl Çalışır sayfasına, özellikle de Her Yerdeki Özel Durumlara bakın! Bölüm.


Sıralama işlevi yerine bir sıralama anahtarına ihtiyacınız varsa, aşağıdaki formüllerden birini kullanın.

>>> 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
Ayrıca, sayı sonunda olmadığında natsort'un da doğru sıralanmasının oldukça ilginç olduğunu düşünüyorum: dosya adları için genellikle böyle. Aşağıdaki örneği eklemekten çekinmeyin: pastebin.com/9cwCLdEK
Martin Thoma

1
Natsort harika bir kütüphanedir, python standart kütüphanesine eklenmelidir! :-)
Mitch McMabers

natsortayrıca 'doğal olarak' dizelerde birden fazla ayrı sayıyı ele alır. Harika şeyler!
FlorianH

182

Bunu dene:

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)

Çıktı:

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

Buradan uyarlanan kod: İnsanlar için sıralama: Doğal sıralama düzeni .


2
neden kullanıyorsunuz return sorted(l, key)yerine l.sort(key)? Herhangi bir performans artışı için mi yoksa sadece daha pitonik mi?
jperelli

12
@jperelli Bence merdiveni arayan kişinin orijinal listesini değiştirecekti. Ancak büyük olasılıkla arayan, listenin başka bir sığ kopyasını istiyor.
huggie

3
Sadece kayıt için, bu tüm girişleri işleyemez: str / int bölmeleri sıralanmalıdır, aksi takdirde ["foo0" girişi için ["foo", 0] <[0, "foo"] gibi karşılaştırmalar oluşturacaksınız. "," 0foo "]. Bu, TypeError değerini yükseltir.
user19087

4
@ user19087: Aslında işe yarıyor, çünkü re.split('([0-9]+)', '0foo')geri dönüyor ['', '0', 'foo']. Bu nedenle, dizeler her zaman dizideki tek dizinlerde ve tamsayılar dizideki tek dizinlerde olacaktır.
Florian Kusche

Performansı merak eden herkes için bu, python'un yerel türünden oldukça yavaştır. yani 25 -50x daha yavaş. Ve her zaman [elm1, elm2, Elm2, elm2] 'yi [elm1, Elm2, elm2, elm2] olarak güvenilir bir şekilde sıralamak istiyorsanız (alttan önceki kapaklar), o zaman natural_sort'u (sıralanmış (lst)) arayabilirsiniz. Daha verimsiz, ancak tekrarlanabilir bir sıralama elde etmek çok kolay. % 50 hız için normal ifadeyi derleyin. Claudiu'nun cevabında görüldüğü gibi.
Charlie Haley

100

İşte Mark Byer'in cevabının çok daha pythonic bir versiyonu:

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

Şimdi bu işlev bunu kullanan herhangi fonksiyonu gibi bir anahtar olarak kullanılabilir list.sort, sorted, maxvb

Bir lambda olarak:

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

9
re modülü derler ve önbelleğe alır otomatik olarak regexes, bu yüzden önceden
derlemeye

1
@ wim: son X kullanımlarını önbelleğe alır, bu nedenle X + 5 normal ifadelerini kullanmak ve daha sonra doğal bir sıralama yapmak mümkündür, bu noktada bu önbelleğe alınmaz. ama uzun vadede muhtemelen ihmal edilebilir
Claudiu

Bunu ben yapmadım, ama belki de nedeni, normal bir python türü gibi tuple'leri işleyememesiydi.
Unfun Kedisi

1
@Claudiu tarafından bahsedilen X kullanımları Python 2.7'de 100 ve Python 3.4'te 512 gibi görünüyor . Ayrıca, sınıra ulaşıldığında önbellek tamamen temizlendiğine dikkat edin (bu nedenle atılan en eski dosya değil).
Zitrax

@Zitrax Önbelleği tamamen temizlemek neden / nasıl mantıklı?
Joschua

19

Http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html dosyasını temel alan ve hala kendi 'anahtar' parametrenize geçme özelliğini ekleyen bir işlev yazdım . Daha karmaşık nesneler (sadece dizeleri) içeren listeler doğal bir tür gerçekleştirmek için buna ihtiyacım var.

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)

Örneğin:

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

Bunu yapmanın daha basit bir yolu tanımlamak olacaktır natural_sort_keyve daha sonra bir listeyi sıralarken anahtarlarınızı zincirleyebilirsiniz, örneğin:list.sort(key=lambda el: natural_sort_key(el['name']))
Claudiu

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

Verileri analiz edelim. Tüm elemanların rakam kapasitesi 2'dir. Ve ortak hazırda 3 harf vardır 'elm'.

Böylece, elemanın maksimum uzunluğu 5'tir. Emin olmak için bu değeri artırabiliriz (örneğin, 8'e).

Bunu göz önünde bulundurarak, tek satırlık bir çözümümüz var:

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

düzenli ifadeler ve harici kütüphaneler olmadan!

print(data)

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

Açıklama:

for elm in data:
    print('{0:0>8}'.format(elm).lower())

>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13

1
Bu, dinamik / bilinmeyen uzunluk verilerini işlemez. Ayrıca, sonunda veriye karşılık gelen verilerde bulunan veriler için diğer çözümlerden farklı şekilde sıralar. * Bu istenmeyen bir durum olmakla birlikte, belirtmek iyi bir şey.
JerodG

1
Dinamik uzunluk verilerini işlemeniz gerekiyorsa width = max(data, key=len), 8yukarıdakiler için ne verileceğini hesaplamak ve daha sonra'{0:0>{width}}'.format(x, width=width)
roganartu

1
Sadece bu forumdaki diğerlerine kıyasla zamanlanmış bir test yaparak, bu çözüm, @snakile'nin işlemeye çalıştığı veri türü için en hızlı ve en verimli olanıdır
SR Colledge

13

Verilen:

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

SergO'nun çözümüne benzer şekilde, harici kütüphaneleri olmayan 1 astar :

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

veya

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

Açıklama:

Bu çözüm , sıralamanın temel özelliğini kullanır için kullanılacak bir işlevi tanımlamak için sort'in . Her veri girişinden önce 'karaağaç' olduğunu bildiğimiz için, sıralama işlevi dizenin 3. karakterden sonraki kısmını tamsayıya dönüştürür (yani int (x [3:])). Verilerin sayısal kısmı farklı bir konumdaysa, işlevin bu kısmı değişmelidir.

Şerefe


6
Ve şimdi daha fazla bir şey için * zarif (pitonik) -sadece bir dokunuş

Orada birçok uygulama var ve bazıları yaklaşsa da, hiçbiri modern python'un sağladığı zarafeti tam olarak yakalamadı.

  • Python (3.5.1) kullanılarak test edildi
  • Sayılar orta dizgideyken çalıştığını göstermek için ek bir liste dahil
  • Test yapmadım, ancak listeniz oldukça büyükse, normal ifadeyi derlemenin daha verimli olacağını varsayıyorum.
    • Eminim bu yanlış bir varsayımsa birisi beni düzeltir

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)])
Tam Kod
#!/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']

Dikkat kullanırken

  • from os.path import split
    • ithalatı farklılaştırmanız gerekecek

İlham kaynağı


6

Bu Gönderinin Değeri

Demek istediğim genel olarak uygulanabilecek regex olmayan bir çözüm sunmak.
Üç işlev oluşturacağım:

  1. find_first_digit@AnuragUniyal'dan ödünç aldım . Bir dizgideki ilk basamağın ya da basamağın olmayan konumunu bulur.
  2. split_digitsbir dizgiyi basamaklı ve basamaksız parçalara ayıran bir jeneratördür. yieldBir rakam olduğunda da tamsayı olacaktır .
  3. natural_keysadece split_digitsa tuple. Bu bizim için bir anahtar olarak kullanmak budur sorted, max, min.

Fonksiyonlar

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))

Çok basamaklı parçalara sahip olmamızın genel olduğunu görebiliriz:

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

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

Veya büyük / küçük harfe duyarlı olarak bırakın:

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

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

OP'nin listesini uygun sırada sıraladığını görebiliriz

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

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

Ancak daha karmaşık listeleri de işleyebilir:

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

Normal ifadem eşdeğeri

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
Çok teşekkürler! Ancak, "12345_A" ve "12345_A2" varsa, ikincisinin ilkinden önce sıralanacağını eklemek istiyorum. En azından Windows böyle yapmıyor. Yine de yukarıdaki sorun için çalışıyor!
morph3us

4

Bir seçenek, dizeyi bir tuple haline getirmek ve genişletilmiş formu kullanarak rakamları değiştirmek http://wiki.answers.com/Q/What_does_expanded_form_mean

bu şekilde a90 ("a", 90,0) ve a1 ("a", 1) olur

Aşağıda bazı örnek kodlar bulunmaktadır (bu, 0'ların başındaki sayıları kaldırması nedeniyle çok verimli değildir)

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)

çıktı:

('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
Ne yazık ki, bu çözüm yalnızca Python 2.X için çalışır. Python 3 için ('b', 1) < ('b', 'e', 't', 'a', 1, '.', 1)geri dönecekTypeError: unorderable types: int() < str()
SethMMorton

@SethMMorgon haklı, bu kod Python 3'te kolayca kırılıyor. Doğal alternatif natsort, pypi.org/project/natsort
FlorianH

3

Buradaki cevaplara dayanarak natural_sorted, yerleşik işlev gibi davranan bir işlev yazdım 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)

Kaynak kodu GitHub snippet veri havuzumda da mevcuttur: https://github.com/bdrung/snippets/blob/master/natural_sorted.py


2

Yukarıdaki cevaplar gösterilen spesifik örnek için iyidir , ancak doğal sıralama ile ilgili daha genel bir soru için birkaç yararlı vakayı kaçırmayın. Bu vakalardan biriyle birazcık aldım, bu yüzden daha kapsamlı bir çözüm yarattım:

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

Test kodu ve çeşitli bağlantılar (StackOverflow üzerinde ve dışında) burada: http://productarchitect.com/code/better-natural-sort.py

Geribildirim hoş geldiniz. Bu kesin bir çözüm demek değildir; sadece bir adım ileri.


Bağlandığınız natsortedve humansortedyanlış kullanıldıkları için başarısız olduğunuz test komut dosyasında ... natsortedbir anahtar olarak geçmeye çalıştınız, ancak aslında sıralama işlevinin kendisi. Denemeliydin natsort_keygen().
SethMMorton

2

Büyük olasılıkla functools.cmp_to_key()python'un türünün altında yatan uygulama ile yakından ilişkilidir. Ayrıca, cmp parametresi eski. Modern yol, girdi öğelerini istenen zengin karşılaştırma işlemlerini destekleyen nesnelere dönüştürmektir.

CPython 2.x altında, ilgili zengin karşılaştırma işleçleri uygulanmamış olsa bile, farklı türdeki nesneler sipariş edilebilir. CPython 3.x altında, farklı türdeki nesnelerin karşılaştırmayı açıkça desteklemesi gerekir. Bkz. Python dizeyi ve int'i nasıl karşılaştırır? hangi resmi belgelere bağlantılar . Cevapların çoğu bu örtülü sıralamaya bağlıdır. Python 3.x'e geçmek, sayılar ve dizeler arasındaki karşılaştırmaları uygulamak ve birleştirmek için yeni bir tür gerektirir.

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()

Üç farklı yaklaşım vardır. İlki, Python'un Iterablekarşılaştırma algoritmasından yararlanmak için iç içe sınıfları kullanır . İkincisi bu yuvalamayı tek bir sınıfa açar. Üçüncüsü strperformansa odaklanmak için alt sınıflamadan önce gelir. Hepsi zamanlıdır; ikincisi iki kat daha hızlı, üçüncüsü ise neredeyse altı kat daha hızlı. Alt sınıflama strgerekli değildir ve ilk etapta muhtemelen kötü bir fikirdir, ancak bazı kolaylıklar ile birlikte gelir.

Sıralama karakterleri büyük / küçük harf sırasını zorlamak için çoğaltılır ve küçük harfleri önce sıralamaya zorlamak için büyük / küçük harf değişimi yapılır; bu "doğal sıralama" nın tipik tanımıdır. Gruplamanın türüne karar veremedim; bazıları aşağıdakileri tercih edebilir, bu da önemli performans avantajları getirir:

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

Kullanıldığı yerlerde karşılaştırma işleçleri, tarafından göz ardıobject edilmeyecek şekildefunctools.total_ordering ayarlanır .

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

Doğal sıralama hem oldukça karmaşıktır hem de belirsiz bir problem olarak tanımlanmıştır. unicodedata.normalize(...)Önceden koşmayı unutmayın ve kullanmak str.casefold()yerine kullanmayı düşünün str.lower(). Muhtemelen dikkate almadığım ince kodlama sorunları var. Bu yüzden natsort kütüphanesini geçici olarak öneriyorum . Github deposuna hızlıca baktım; kod bakımı mükemmeldi.

Gördüğüm tüm algoritmalar, karakterleri çoğaltma ve indirme ve durum değiştirme gibi numaralara bağlıdır. Bu, çalışma süresini iki katına çıkarırken bir alternatif, girdi karakter kümesinde tam bir doğal sıralama gerektirir. Bunun unicode belirtiminin bir parçası olduğunu düşünmüyorum ve bundan çok daha fazla unicode basamak olduğundan [0-9], böyle bir sıralama oluşturmak eşit derecede göz korkutucu olacaktır. Yerel ayarlara uygun karşılaştırmalar istiyorsanız, dizelerinizi locale.strxfrmPython'un Sıralama NASIL bölümüne göre hazırlayın .


1

Bu ihtiyacı kendim kabul edeyim:

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))

Şimdi böyle bir listeye sahipsek:

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

key=Doğal bir tür yapmak için kwarg'u kullanabiliriz :

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

Buradaki dezavantaj elbette, şimdi olduğu gibi, işlev büyük harfleri küçük harflerden önce sıralayacaktır.

Vakaya duyarsız bir orfoz uygulamasını okuyucuya bırakacağım :-)


0

İstediğiniz listeye ulaşmak için sadece keyanahtar kelime argümanını kullanmanızı öneririm. Örneğin: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
bu rakamları işlemez. a_51sonra olacağını a500500> 51 da,
skjerns

Doğru, cevabım verilen Elm11 ve elm1 örneğiyle eşleşiyor. Özellikle doğal sıralama talebini kaçırdı ve işaretli cevap muhtemelen en iyisi :)
Johny Vaknin

0

@ Mark Byers yanıtını takiben, keyparametreyi kabul eden ve daha fazla PEP8 uyumlu bir uyarlama .

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)

Ben de bir Gist yaptım


(-1) bu cevap Mark'ınkine göre yeni bir şey getirmez (herhangi bir linter bazı kodları PEP8-ify yapabilir). Veya keyparametre? Ancak bu da @ beauburrier'ın cevabında örneklenmiştir
Ciprian Tomoiagă

0

Claudiu'nun Mark Byer'in cevabı üzerindeki iyileşmesi ;-)

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, belki herkes fonksiyon argümanı varsayılanlarının defzamanında değerlendirildiğini hatırlamaz


-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

Teşekkür :

Kabarcık Sıralama Ödevi

Python'da her seferinde bir harf bir dize nasıl okunur


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

4
Uygulamanız yalnızca sayı sorununu çözer. Dizelerde sayı yoksa uygulama başarısız olur. Örneğin ['sessiz', 'hayalet'] üzerinde deneyin (dizin dizini aralık dışında).
snakile

2
@snaklie: sorunuz iyi bir örnek olay sağlayamıyor. Ne yapmaya çalıştığınızı açıklamadınız ve sorunuzu bu yeni bilgilerle güncellemediniz. Denediğin bir şey göndermedin, bu yüzden lütfen telepati girişimimi bu kadar küçümsemeyin.
SilentGhost

5
@SilentGhost: Öncelikle size bir oy verdim çünkü cevabınızın yararlı olduğunu düşünüyorum (sorunumu çözmese bile). İkincisi, olası tüm vakaları örneklerle kapatamıyorum. Bence doğal türlere oldukça açık bir tanım verdim. Böyle basit bir kavrama karmaşık bir örnek veya uzun bir tanım vermenin iyi bir fikir olduğunu düşünmüyorum. Soruna daha iyi bir formülasyon düşünebilirseniz sorumu düzenleyebilirsiniz.
snakile

1
@SilentGhost: Bu tür dizelerle, dosyaları dosyaları ada göre sıraladığında (vakaları yoksay, vb.) Windows'un bu dosya adları ile aynı şekilde ele almak istiyorum. Benim için açık gibi görünüyor, ama söylediğim her şey benim için açık görünüyor, bu yüzden açık olup olmadığını yargılamıyorum.
snakile

1
@snakile, yakın tanımlayıcı doğal aramanın yakınında hiçbir yere gelmediniz. Bunu yapmak oldukça zor ve çok fazla ayrıntı gerektiriyordu. Windows explorer tarafından kullanılan sıralama düzeni istiyorsanız bunu sağlayan basit bir api çağrısı olduğunu biliyor musunuz?
David Heffernan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.