tabla - organizar datos python
¿Python tiene una función incorporada para el ordenamiento natural de cadenas? (15)
Utilizando Python 3.x, tengo una lista de cadenas para las que me gustaría realizar una ordenación alfabética natural.
Ordenación natural: el orden por el cual se ordenan los archivos en Windows.
Por ejemplo, la siguiente lista está ordenada naturalmente (lo que quiero):
[''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''Elm12'', ''elm13'']
Y aquí está la versión "ordenada" de la lista anterior (lo que tengo):
[''Elm11'', ''Elm12'', ''Elm2'', ''elm0'', ''elm1'', ''elm10'', ''elm13'', ''elm9'']
Estoy buscando una función de ordenación que se comporte como la primera.
Valor de esta publicación
Mi punto es ofrecer una solución no regex que pueda aplicarse en general.
Voy a crear tres funciones:
-
find_first_digit
que tomé prestado de @AnuragUniyal . Encontrará la posición del primer o no dígito en una cadena. -
split_digits
que es un generador que separa una cadena en trozos de dígitos y no dígitos. Tambiényield
números enteros cuando es un dígito. -
natural_key
simplemente envuelvesplit_digits
en unatuple
. Esto es lo que usamos como clave parasorted
,max
,min
.
Funciones
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))
Podemos ver que es general en que podemos tener fragmentos de varios dígitos:
# Note that the key has lower case letters
natural_key(''asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh'')
(''asl;dkfdfkj:sdlkfjdf'', 809, ''lkasdjfa_'', 543, ''_hh'')
O dejar como mayúsculas y minúsculas:
natural_key(''asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh'', True)
(''asl;dkfDFKJ:sdlkfjdf'', 809, ''lkasdjfa_'', 543, ''_hh'')
Podemos ver que ordena la lista de OP en el orden apropiado
sorted(
[''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''Elm12'', ''elm13''],
key=natural_key
)
[''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''Elm12'', ''elm13'']
Pero también puede manejar listas más complicadas:
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'']
Mi equivalente de expresiones regulares sería
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))
Aquí hay una versión mucho más pitónica de la respuesta de 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)]
Ahora esta función se puede utilizar como una clave en cualquier función que la use, como list.sort
, sorted
, max
, etc.
Como un lambda:
lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split(''(/d+)'', s)]
Basándome en las respuestas aquí, escribí una función natural_sorted
que se comporta como la función incorporada sorted
:
# Copyright (C) 2018, Benjamin Drung <[email protected]>
#
# 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)
El código fuente también está disponible en mi repositorio de fragmentos de GitHub: https://github.com/bdrung/snippets/blob/master/natural_sorted.py
Dado:
data=[''Elm11'', ''Elm12'', ''Elm2'', ''elm0'', ''elm1'', ''elm10'', ''elm13'', ''elm9'']
Similar a la solución de SergO, un 1-liner sin bibliotecas externas sería :
data.sort(key=lambda x : int(x[3:]))
o
sorted_data=sorted(data, key=lambda x : int(x[3:]))
Explicación:
Esta solución utiliza la característica clave de clasificación para definir una función que se empleará para la clasificación. Como sabemos que cada entrada de datos está precedida por ''elm'', la función de clasificación se convierte en una parte entera de la cadena después del tercer carácter (es decir, int (x [3:])). Si la parte numérica de los datos está en una ubicación diferente, entonces esta parte de la función tendría que cambiar.
Aclamaciones
Escribí una función basada en http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html que agrega la capacidad de pasar aún su propio parámetro ''clave''. Necesito esto para realizar un tipo natural de listas que contengan objetos más complejos (no solo cadenas).
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)
Por ejemplo:
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''}]
Hay una biblioteca de terceros para esto en PyPI llamada natsort (divulgación completa, soy el autor del paquete). Para su caso, puede hacer cualquiera de los siguientes:
>>> 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'']
Debe tener en cuenta que natsort
utiliza un algoritmo general, por lo que debería funcionar para casi cualquier entrada que le natsort
. Si desea obtener más detalles sobre por qué puede elegir una biblioteca para hacer esto en lugar de utilizar su propia función, consulte la natsort
Cómo funciona de la documentación de natsort
, en particular, ¡ Casos especiales en todas partes! sección.
Si necesita una clave de clasificación en lugar de una función de clasificación, utilice una de las fórmulas siguientes.
>>> 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'']
Las respuestas anteriores son buenas para el ejemplo específico que se mostró, pero faltan varios casos útiles para la pregunta más general de tipo natural. Acabo de ser mordido por uno de esos casos, así que creé una solución más completa:
def natural_sort_key(string_or_number):
"""
by Scott S. Lawton <[email protected]> 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
El código de prueba y varios enlaces (dentro y fuera de ) están aquí: http://productarchitect.com/code/better-natural-sort.py
Comentarios bienvenidos. Eso no pretende ser una solución definitiva; Solo un paso adelante.
Le sugiero que simplemente use el argumento de la palabra key
clave sorted
para lograr la lista deseada
Por ejemplo:
to_order= [e2,E1,e5,E4,e3]
ordered= sorted(to_order, key= lambda x: x.lower())
# ordered should be [E1,e2,e3,E4,e5]
Lo más probable es que functools.cmp_to_key()
esté estrechamente vinculado a la implementación subyacente de la ordenación de python. Además, el parámetro cmp es legado. La forma moderna es transformar los elementos de entrada en objetos que admiten las ricas operaciones de comparación deseadas.
Bajo CPython 2.x, se pueden ordenar objetos de diferentes tipos incluso si no se han implementado los respectivos operadores de comparación ricos. Bajo CPython 3.x, los objetos de diferentes tipos deben admitir explícitamente la comparación. Ver ¿Cómo Python compara la cadena y el int? que enlaza a la documentación oficial . La mayoría de las respuestas dependen de este ordenamiento implícito. Cambiar a Python 3.x requerirá un nuevo tipo para implementar y unificar comparaciones entre números y cadenas.
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()
Hay tres enfoques diferentes. El primero utiliza clases anidadas para aprovechar el algoritmo de comparación de Iterable
de Python. El segundo desenrolla este anidamiento en una sola clase. La tercera parte de subclasificación str
centran en el rendimiento. Todos son cronometrados; el segundo es el doble de rápido y el tercero casi seis veces más rápido. La subclasificación no es obligatoria, y probablemente fue una mala idea en primer lugar, pero viene con ciertas conveniencias.
Los caracteres de clasificación se duplican para forzar el ordenamiento por caso, y se intercambian entre mayúsculas y minúsculas para forzar a la letra minúscula a ordenar primero; Esta es la definición típica de "género natural". No pude decidir sobre el tipo de agrupación; Algunos pueden preferir lo siguiente, lo que también trae importantes beneficios de rendimiento:
d = lambda s: s.lower()+s.swapcase()
Cuando se utilizan, los operadores de comparación se establecen en el de object
para que no sean ignorados por 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
La clasificación natural es bastante complicada y vagamente definida como un problema. No olvide ejecutar unicodedata.normalize(...)
antemano, y considere usar str.casefold()
lugar de str.lower()
. Probablemente hay problemas de codificación sutiles que no he considerado. Así que tentativamente recomiendo la biblioteca natsort . Eché un vistazo rápido al repositorio de github; El código de mantenimiento ha sido estelar.
Todos los algoritmos que he visto dependen de trucos como la duplicación y reducción de caracteres y el intercambio de mayúsculas y minúsculas. Si bien esto duplica el tiempo de ejecución, una alternativa requeriría un orden natural total en el conjunto de caracteres de entrada. No creo que esto sea parte de la especificación de Unicode, y dado que hay muchos más dígitos de Unicode que [0-9]
, crear tal clasificación sería igualmente desalentador. Si desea comparaciones locale.strxfrm
configuración regional, prepare sus cadenas con locale.strxfrm
según la Clasificación de Python CÓMO .
Prueba esto:
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)
Salida:
[''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''Elm12'', ''elm13'']
ideone trabajando en línea: ideone .
Código adaptado de aquí: Clasificación para humanos: orden natural .
Una opción es convertir la cadena en una tupla y reemplazar los dígitos mediante el formulario expandido http://wiki.answers.com/Q/What_does_expanded_form_mean
de esa manera a90 se convertiría en ("a", 90,0) y a1 se convertiría en ("a", 1)
A continuación se muestra un código de ejemplo (que no es muy eficiente debido a la forma en que elimina los 0 iniciales de los números)
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)
salida:
(''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'']
>>> import re
>>> sorted(lst, key=lambda x: int(re.findall(r''/d+$'', x)[0]))
[''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''Elm12'', ''elm13'']
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
Agradecimientos :
data = [''elm13'', ''elm9'', ''elm0'', ''elm1'', ''Elm11'', ''Elm2'', ''elm10'']
Analicemos los datos. La capacidad de dígitos de todos los elementos es 2. Y hay 3 letras en la parte literal ''elm''
.
Por lo tanto, la longitud máxima del elemento es 5. Podemos aumentar este valor para asegurarnos (por ejemplo, a 8).
Teniendo eso en cuenta, tenemos una solución de una línea:
data.sort(key=lambda x: ''{0:0>8}''.format(x).lower())
Sin expresiones regulares y bibliotecas externas!
print(data)
>>> [''elm0'', ''elm1'', ''Elm2'', ''elm9'', ''elm10'', ''Elm11'', ''elm13'']
Explicación:
for elm in data:
print(''{0:0>8}''.format(elm).lower())
>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13
Hay muchas implementaciones por ahí, y aunque algunas se han acercado, ninguna ha capturado la elegancia que ofrece la pitón moderna.
- Probado utilizando python (3.5.1)
- Incluye una lista adicional para demostrar que funciona cuando los números están en la mitad de la cadena
- No probé, sin embargo, asumo que si su lista fuera considerable, sería más eficiente compilar la expresión regular de antemano
- Estoy seguro de que alguien me corregirá si se trata de una suposición errónea
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)])
Código Completo
#!/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'']
Precaución al usar
-
from os.path import split
- Necesitarás diferenciar las importaciones.
Inspiración de
- Documentación de Python: clasificación CÓMO
- Clasificación para los humanos: orden natural
- Clasificación humana
- Contribuidores / comentaristas a esto y publicaciones referenciadas