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 Iterable
karşı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ü str
performansa 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 str
gerekli 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.strxfrm
Python'un Sıralama NASIL bölümüne göre hazırlayın .