¿Existe una función integrada para la clasificación natural de cadenas?
Tengo una lista de cadenas para las que me gustaría realizar una ordenación alfabética natural .
Por ejemplo, la siguiente lista está ordenada de forma natural (lo que quiero):
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Y aquí está la versión "ordenada" de la lista anterior (lo que uso sorted()
):
['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
Estoy buscando una función de clasificación que se comporte como la primera.
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 las siguientes cosas:
>>> 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']
Debes tener en cuenta que natsort
utiliza un algoritmo general, por lo que debería funcionar para casi cualquier entrada que le introduzcas. Si desea obtener más detalles sobre por qué podría elegir una biblioteca para hacer esto en lugar de implementar su propia función, consulte la página Cómo funcionanatsort
de la documentación , 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 cualquiera 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']
Actualización noviembre 2020
Dado que una solicitud/pregunta popular es "¿cómo ordenar como el Explorador de Windows?" (o cualquiera que sea el navegador del sistema de archivos de su sistema operativo), a partir de natsort
la versión 7.1.0 hay una función llamada os_sorted
para hacer exactamente esto. En Windows, se ordenará en el mismo orden que el Explorador de Windows, y en otros sistemas operativos debería ordenarse como el navegador del sistema de archivos local.
>>> from natsort import os_sorted
>>> os_sorted(list_of_paths)
# your paths sorted like your file system browser
Para aquellos que necesitan una clave de clasificación, pueden usar os_sort_keygen
(o os_sort_key
si solo necesitan los valores predeterminados).
Advertencia : lea la documentación de la API para esta función antes de usarla para comprender las limitaciones y cómo obtener mejores resultados.
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)
Producción:
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
Código adaptado de aquí: Clasificación para humanos: orden de clasificación natural .
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 usar como clave en cualquier función que la use, como list.sort
, sorted
, max
etc.
Como lambda:
lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]
Código de demostración totalmente reproducible:
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']
Analicemos los datos. La capacidad de dígitos de todos los elementos es 2. Y hay 3 letras en la parte literal común 'elm'
.
Entonces, la longitud máxima del elemento es 5. Podemos aumentar este valor para asegurarnos (por ejemplo, a 8).
Teniendo esto en cuenta, tenemos una solución de una sola línea:
data.sort(key=lambda x: '{0:0>8}'.format(x).lower())
¡Sin expresiones regulares ni 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
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 seguir pasando su propio parámetro 'clave'. Necesito esto para realizar una clasificación natural de listas que contienen 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'}]