Usando Python 3.x, tengo una lista de cadenas para las cuales me gustaría realizar una ordenación alfabética natural.
Ordenación natural: el orden en que se ordenan los archivos en Windows.
Por ejemplo, la siguiente lista está naturalmente ordenada (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 clasificación que se comporte como la primera.
python
sorting
python-3.x
snakile
fuente
fuente
!1, 1, !a, a
. La única forma de ordenar como Windows parece ser usar laStrCmpLogicalW
función de Windows en sí, ya que nadie parece haber implementado esta función correctamente (se agradecería la fuente). Solución: stackoverflow.com/a/48030307/2441026Respuestas:
Hay una biblioteca de terceros para esto en PyPI llamada natsort (divulgación completa, soy el autor del paquete). Para su caso, puede hacer lo siguiente:
Debe tener en cuenta que
natsort
utiliza un algoritmo general, por lo que debería funcionar para casi cualquier entrada que le arroje. 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 funciona de lanatsort
documentación , en particular los Casos especiales en todas partes. sección.Si necesita una clave de clasificación en lugar de una función de clasificación, use cualquiera de las fórmulas a continuación.
fuente
natsort
también 'naturalmente' maneja el caso de múltiples números separados en las cadenas. ¡Buena cosa!Prueba esto:
Salida:
Código adaptado desde aquí: Clasificación para humanos: orden de clasificación natural .
fuente
return sorted(l, key)
lugar del.sort(key)
? ¿Es para ganar rendimiento o simplemente para ser más pitónico?re.split('([0-9]+)', '0foo')
regresa['', '0', 'foo']
. Debido a eso, las cadenas siempre estarán en índices pares y enteros en índices impares en la matriz.Aquí hay una versión mucho más pitónica de la respuesta de Mark Byer:
Ahora bien, esta función se puede utilizar como una clave en cualquier función que lo utiliza, como
list.sort
,sorted
,max
, etc.Como lambda:
fuente
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 su propio parámetro 'clave'. Necesito esto para realizar un tipo natural de listas que contienen objetos más complejos (no solo cadenas).
Por ejemplo:
fuente
natural_sort_key
, y luego, al ordenar una lista, podría encadenar sus llaves, por ejemplo:list.sort(key=lambda el: natural_sort_key(el['name']))
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 línea:
sin expresiones regulares y bibliotecas externas!
Explicación:
fuente
width = max(data, key=len)
para calcular qué8
'{0:0>{width}}'.format(x, width=width)
Dado:
Similar a la solución de SergO, un 1-liner sin bibliotecas externas sería :
o
Explicación:
Esta solución utiliza la característica clave de sort 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 un número entero en la parte de la cadena después del 3er 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.
Salud
fuente
Hay muchas implementaciones por ahí, y aunque algunas se han acercado, ninguna captó la elegancia que ofrece la moderna Python.
Precaución al usar
from os.path import split
Inspiración de
fuente
Valor de esta publicación
Mi punto es ofrecer una solución no regex que se pueda aplicar en general.
Crearé tres funciones:
find_first_digit
que tomé prestado de @AnuragUniyal . Encontrará la posición del primer dígito o no dígito en una cadena.split_digits
que es un generador que separa una cadena en fragmentos de dígitos y no dígitos. También seráyield
un número entero cuando sea un dígito.natural_key
simplemente se envuelvesplit_digits
en untuple
. Esto es lo que usamos como una clave parasorted
,max
,min
.Las funciones
Podemos ver que es general, ya que podemos tener fragmentos de varios dígitos:
O dejar como mayúsculas y minúsculas:
Podemos ver que ordena la lista de OP en el orden apropiado
Pero también puede manejar listas más complicadas:
Mi equivalente de expresiones regulares sería
fuente
Una opción es convertir la cadena en una tupla y reemplazar los dígitos usando la forma expandida http://wiki.answers.com/Q/What_does_expanded_form_mean
de esa manera a90 se convertiría ("a", 90,0) y a1 se convertiría ("a", 1)
a continuación hay un código de muestra (que no es muy eficiente debido a la forma en que elimina los ceros a la izquierda de los números)
salida:
fuente
('b', 1) < ('b', 'e', 't', 'a', 1, '.', 1)
volveráTypeError: unorderable types: int() < str()
natsort
, pypi.org/project/natsortBasado en las respuestas aquí, escribí una
natural_sorted
función que se comporta como la función incorporadasorted
: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
fuente
Las respuestas anteriores son buenas para el ejemplo específico que se mostró, pero pierden 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:
El código de prueba y varios enlaces (dentro y fuera de StackOverflow) 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
fuente
natsorted
yhumansorted
falla porque se usaron incorrectamente ... intentó pasarnatsorted
como clave pero en realidad es la función de clasificación en sí. Deberías haberlo intentadonatsort_keygen()
.Lo más probable
functools.cmp_to_key()
es que esté estrechamente relacionado con la implementación subyacente del género Python. Además, el parámetro cmp es heredado. La forma moderna es transformar los elementos de entrada en objetos que admitan las operaciones de comparación enriquecidas deseadas.Bajo CPython 2.x, se pueden ordenar objetos de tipos dispares incluso si los respectivos operadores de comparación enriquecida no se han implementado. En CPython 3.x, los objetos de diferentes tipos deben admitir explícitamente la comparación. Consulte ¿Cómo compara Python string e int? que enlaza con la documentación oficial . La mayoría de las respuestas dependen de este orden implícito. Cambiar a Python 3.x requerirá un nuevo tipo para implementar y unificar las comparaciones entre números y cadenas.
Hay tres enfoques diferentes. El primero usa clases anidadas para aprovechar el
Iterable
algoritmo de comparación de Python . El segundo desenrolla este anidamiento en una sola clase. El tercero renuncia a la subclasestr
para centrarse en el rendimiento. Todos son cronometrados; el segundo es dos veces más rápido, mientras que el tercero es casi seis veces más rápido.str
No se requiere subclasificar , y probablemente fue una mala idea en primer lugar, pero viene con ciertas comodidades.Los caracteres de clasificación se duplican para forzar el pedido por mayúsculas y se intercambian entre mayúsculas y minúsculas para forzar que las letras minúsculas se ordenen primero; Esta es la definición típica de "género natural". No pude decidir sobre el tipo de agrupación; algunos podrían preferir lo siguiente, que también trae importantes beneficios de rendimiento:
Cuando se utilizan, los operadores de comparación se configuran para que
object
no sean ignoradosfunctools.total_ordering
.La ordenación natural es bastante complicada y vagamente definida como un problema. No olvides correr de
unicodedata.normalize(...)
antemano, y considera el uso enstr.casefold()
lugar de hacerlostr.lower()
. Probablemente hay problemas sutiles de codificación que no he considerado. Por lo tanto, recomiendo tentativamente la biblioteca natsort . Eché un vistazo rápido al repositorio de github; El mantenimiento del código ha sido estelar.Todos los algoritmos que he visto dependen de trucos como duplicar y bajar caracteres, y cambiar 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 Unicode, y dado que hay muchos más dígitos Unicode que
[0-9]
, crear tal clasificación sería igualmente desalentador. Si desea realizar comparaciones locales, prepare sus cadenas con ellocale.strxfrm
método de clasificación de Python .fuente
Permítanme presentar mi propia opinión sobre esta necesidad:
Ahora si tenemos la lista como tal:
Simplemente podemos usar el
key=
kwarg para hacer un tipo natural:El inconveniente aquí es, por supuesto, como lo es ahora, la función clasificará las letras mayúsculas antes que las minúsculas.
Dejaré la implementación de un mero que no distingue entre mayúsculas y minúsculas para el lector :-)
fuente
Le sugiero que simplemente use el
key
argumento de palabra clave desorted
para lograr su lista deseada.Por ejemplo:
fuente
a_51
sería despuésa500
, aunque 500> 51Siguiendo la respuesta de @Mark Byers, aquí hay una adaptación que acepta el
key
parámetro y es más compatible con PEP8.También hice un Gist
fuente
key
parámetro? Pero esto también se ejemplifica en la respuesta de @ beauburrierUna mejora en la mejora de Claudiu en la respuesta de Mark Byer ;-)
Por cierto, tal vez no todos recuerden que los valores predeterminados del argumento de la función se evalúan en el
def
momentofuente
Agradecimientos :
Tarea tipo burbuja
Cómo leer una cadena letra por letra en python
fuente
fuente