Ordenar una lista secundaria de elementos en una lista dejando el rest en su lugar

Digamos que tengo una lista ordenada de cadenas como en:

['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 

Ahora quiero ordenar según el valor numérico final para las B s, así que tengo:

 ['A', 'B' , 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 

Un posible algoritmo sería regex = re.compile(ur'(B)(\d*)) una expresión regular como regex = re.compile(ur'(B)(\d*)) , encontrar los índices de la primera y la última B , cortar la lista, ordenar la porción usando la segunda expresión regular. grupo, a continuación, inserte la rebanada ordenada. Sin embargo, esto parece demasiado complicado. ¿Hay una manera de escribir una función clave que “deja el elemento en su lugar” si no coincide con la expresión regular y solo ordena los elementos (sublistas) que coinciden?

Nota: lo anterior es solo un ejemplo ; No necesariamente conozco el patrón (o quizás también quiera ordenar las C, o cualquier cadena que tenga un número final ahí). Idealmente, estoy buscando un enfoque para el problema general de clasificar solo las subsecuencias que coinciden con un criterio determinado (o, en su defecto, solo aquellas que cumplen el criterio específico de un prefijo dado seguido de una cadena de dígitos).

En el caso simple en el que solo desea ordenar los dígitos finales numéricamente y sus prefijos sin dígitos alfabéticamente, necesita una función clave que divida cada elemento en componentes sin dígitos y dígitos del siguiente modo:

 'AB123' -> ['AB', 123] 'CD' -> ['CD'] '456' -> ['', 456] 

Nota: En el último caso, la cadena vacía '' no es estrictamente necesaria en CPython 2.x, ya que los enteros ordenan antes que las cadenas, pero eso es un detalle de implementación más que una garantía del lenguaje, y en Python 3.x es necesario , porque las cadenas y los enteros no se pueden comparar en absoluto.

Puede crear una función clave de este tipo utilizando una lista de comprensión y re.split() :

 import re def trailing_digits(x): return [ int(g) if g.isdigit() else g for g in re.split(r'(\d+)$', x) ] 

Aquí está en acción:

 >>> s1 = ['11', '2', 'A', 'B', 'B1', 'B11', 'B2', 'B21', 'C', 'C11', 'C2'] 

 >>> sorted(s1, key=trailing_digits) ['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11'] 

Una vez que agrega la restricción de que solo las cadenas con un prefijo o prefijos en particular tienen sus dígitos finales ordenados numéricamente, las cosas se complican un poco más.

La siguiente función genera y devuelve una función clave que cumple el requisito:

 def prefixed_digits(*prefixes): disjunction = '|'.join('^' + re.escape(p) for p in prefixes) pattern = re.compile(r'(?<=%s)(\d+)$' % disjunction) def key(x): return [ int(g) if g.isdigit() else g for g in re.split(pattern, x) ] return key 

La principal diferencia aquí es que se crea una expresión regular precomstackda (que contiene un aspecto detrás de la construcción del prefijo o prefijos suministrados), y se devuelve una función clave que usa esa expresión regular.

Aquí hay algunos ejemplos de uso:

 >>> s2 = ['A', 'B', 'B11', 'B2', 'B21', 'C', 'C11', 'C2', 'D12', 'D2'] 

 >>> sorted(s2, key=prefixed_digits('B')) ['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D12', 'D2'] 

 >>> sorted(s2, key=prefixed_digits('B', 'C')) ['A', 'B', 'B2', 'B11', 'B21', 'C', 'C2', 'C11', 'D12', 'D2'] 

 >>> sorted(s2, key=prefixed_digits('B', 'D')) ['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D2', 'D12'] 

Si se llama sin argumentos, prefixed_digits() devuelve una función clave que se comporta de manera idéntica a trailing_digits :

 >>> sorted(s1, key=prefixed_digits()) ['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11'] 

Advertencias:

  1. Debido a una restricción en el módulo de re de Python con respecto a la syntax de lookbhehind, los múltiples prefijos deben tener la misma longitud.

  2. En Python 2.x, las cadenas que son puramente numéricas se ordenarán numéricamente sin importar qué prefijos se suministran a prefixed_digits() . En Python 3, causarán una excepción (excepto cuando se llame sin argumentos, o en el caso especial de key=prefixed_digits('') , que ordenará numéricamente cadenas puramente numéricas, y cadenas prefijadas alfabéticamente). Solución que puede ser posible con un regex significativamente más complejo, pero desistí de intentarlo después de unos veinte minutos.

Si entiendo correctamente, su objective final es ordenar las subsecuencias, mientras deja de lado los elementos que no forman parte de las subsecuencias.

En su ejemplo, la subsecuencia se define como elementos que comienzan con “B” . Resulta que su lista de ejemplos contiene elementos en orden lexicográfico, lo cual es un poco demasiado conveniente y puede distraer la búsqueda de una solución generalizada. Mezclamos un poco las cosas utilizando una lista de ejemplos diferente. Qué tal si:

 ['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2'] 

Aquí, los artículos ya no están ordenados (al menos traté de organizarlos para que no lo estén), ni los que comienzan con “B”, ni los otros. Sin embargo, los elementos que comienzan con “B” siguen formando una única sub-secuencia contigua, que ocupa el rango único 1-6 en lugar de los rangos divididos, por ejemplo, como 0-3 y 6-7. Esto podría distraer nuevamente, abordaré ese aspecto más abajo.

Si entiendo su objective final correctamente, le gustaría que esta lista se ordenara así:

 ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2'] 

Para hacer que esto funcione, necesitamos una función clave que devuelva una tupla, tal que:

  • Primer valor:
    • Si el elemento no comienza con “B”, entonces el índice en la lista original (o un valor en el mismo orden)
    • Si el elemento comienza con “B”, entonces el índice del último elemento que no comenzó con “B”
  • Segundo valor:
    • Si el elemento no comienza con “B”, omita esto
    • Si el elemento comienza con “B”, entonces el valor numérico

Esto se puede implementar de esta manera, y con algunos doctests :

 def order_sublist(items): """ >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2']) ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']) ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2'] """ def key(): ord1 = [0] def inner(item): if not item.startswith('B'): ord1[0] += 1 return ord1[0], return ord1[0], int(item[1:] or 0) return inner return sorted(items, key=key()) 

En esta implementación, los elementos se ordenan por estas claves:

 [(1,), (1, 2), (1, 11), (1, 22), (1, 0), (1, 1), (1, 21), (2,), (3,), (4,), (5,)] 

Los elementos que no comienzan por “B” mantienen su orden, gracias al primer valor en la tupla de claves, y los elementos que comienzan con “B” se ordenan gracias al segundo valor de la tupla de claves.

Esta implementación contiene algunos trucos que vale la pena explicar:

  • La función key devuelve una tupla de 1 o 2 elementos, como se explicó anteriormente: los elementos que no son B tienen un valor, los elementos B tienen dos.

  • El primer valor de la tupla no es exactamente el índice original, pero es lo suficientemente bueno. El valor antes del primer elemento B es 1, todos los elementos B usan el mismo valor y los valores después de B obtienen un valor incrementado cada vez. Dado que (1,) < (1, x) < (2,) donde x puede ser cualquier cosa, estas teclas se ordenarán como queríamos.

Y ahora a los trucos "reales" 🙂

  • ¿Qué pasa con ord1 = [0] y ord1[0] += 1 ? Esta es una técnica para cambiar un valor no local en una función. Si hubiera usado simplemente ord1 = 0 y ord1 += 1 no funcionaría, porque ord1 es un valor primitivo definido fuera de la función. Sin la palabra clave global no es visible ni reasignable. Un valor ord1 dentro de la función inner sombrearía el valor primitivo exterior. Pero ord1 es una lista, es visible en el inner y su contenido puede modificarse. Tenga en cuenta que no puede ser reasignado. Si reemplaza con ord1[0] += 1 como ord1 = [ord1[0] + 1] que resultaría en el mismo valor, no funcionaría, ya que en ese caso ord1 en el lado izquierdo es una variable local, sombreado La ord1 en el ámbito exterior, y no modificando su valor.

  • ¿Qué pasa con la key y inner funciones inner ? Pensé que sería bueno si la función clave a la que pasaremos para ser sorted sea ​​reutilizable. Esta versión más simple también funciona:

     def order_sublist(items): ord1 = [0] def inner(item): if not item.startswith('B'): ord1[0] += 1 return ord1[0], return ord1[0], int(item[1:] or 0) return sorted(items, key=inner) 

    La diferencia importante es que si quisiera usar el inner dos veces, ambos usos compartirían la misma lista ord1 . Lo que puede ser aceptable, siempre que el valor entero ord1[0] no se desborde durante el uso. En este caso, no utilizará la función dos veces, e incluso si lo hiciera, probablemente no habría riesgo de desbordamiento de enteros, pero como cuestión de principio, es bueno hacer la función limpia y reutilizable envolviéndola como Lo hice en mi propuesta inicial. Lo que hace la función key es simplemente inicializar ord1 = [0] en su scope, definir la función inner y devolver la función inner . De esta manera ord1 es efectivamente privada, gracias al cierre. Cada vez que llama a key() , devuelve una función que tiene su valor ord1 fresco y ord1 .

  • Por último, pero no menos importante, observe las doctests : el comentario """ ... """ es más que solo documentación, son pruebas ejecutables. Las >>> líneas son código para ejecutar en un shell de Python, y las siguientes líneas son el resultado esperado. Si tiene este progtwig en un archivo llamado script.py , puede ejecutar las pruebas con python -m doctest script.py . Cuando todas las pruebas pasan, no obtienes ningún resultado. Cuando una prueba falla, obtienes un buen informe. Es una excelente manera de verificar que su progtwig funciona, a través de ejemplos demostrados. Puede tener varios casos de prueba, separados por líneas en blanco, para cubrir casos de esquina interesantes. En este ejemplo, hay dos casos de prueba, con su entrada ordenada original, y la entrada sin clasificar modificada.

Sin embargo, como @ zero-piraeus ha hecho una observación interesante:

Puedo ver que su solución se basa en sorted() escaneando la lista de izquierda a derecha (lo cual es razonable; no puedo imaginar que TimSort vaya a ser reemplazado o cambiado radicalmente en cualquier momento pronto) pero no esté garantizado por Python AFAIK, y Hay algoritmos de clasificación que no funcionan así.

Intenté ser autocrítico y dudar de que el escaneo de izquierda a derecha sea razonable. Pero creo que lo es. Después de todo, la clasificación realmente se basa en las claves, no en los valores reales. Creo que lo más probable es que Python haga algo como esto:

  1. Tome una lista de los valores clave con [key(value) for value in input] , visitando los valores de izquierda a derecha.
  2. zip la lista de claves con los elementos originales
  3. Aplique cualquier algoritmo de clasificación en la lista comprimida, comparando elementos por el primer valor del código postal e intercambiando elementos
  4. Al final, devuelva los elementos ordenados con return [t[1] for t in zipped]

Cuando se construye la lista de valores clave, podría funcionar en varios subprocesos, digamos dos, el primer subproceso uno que llena la primera mitad y el segundo subproceso que llena la segunda mitad en paralelo. Eso arruinaría el ord1[0] += 1 . Pero dudo que haga este tipo de optimización, ya que simplemente parece una exageración.

Pero para eliminar cualquier duda, podemos seguir esta estrategia de implementación alternativa, aunque la solución se vuelve un poco más detallada:

 def order_sublist(items): """ >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2']) ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']) ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2'] """ ord1 = 0 zipped = [] for item in items: if not item.startswith('B'): ord1 += 1 zipped.append((ord1, item)) def key(item): if not item[1].startswith('B'): return item[0], return item[0], int(item[1][1:] or 0) return [v for _, v in sorted(zipped, key=key)] 

Tenga en cuenta que gracias a los doctests, tenemos una manera fácil de verificar que la implementación alternativa todavía funciona como antes.


¿Qué tal si quisieras esta lista de ejemplos?

 ['X', 'B', 'B1', 'B11', 'B2', 'B22', 'C', 'Q1', 'C11', 'C2', 'B21'] 

Para ser ordenado de esta manera:

 ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'Q1', 'C11', 'C2', 'B22'] 

Es decir, los elementos que comienzan con "B" ordenados por su valor numérico, incluso cuando no forman una sub-secuencia contigua ?

Eso no será posible con una función de llave mágica. Ciertamente es posible, sin embargo, con un poco más de trabajo de piernas. Tú podrías:

  1. Cree una lista con los índices originales de los elementos que comienzan con "B"
  2. Cree una lista con los elementos que comienzan con "B" y ordénelos de la forma que desee.
  3. Escriba de nuevo el contenido de la lista ordenada en los índices originales

Si necesita ayuda con esta última implementación, hágamelo saber.

La mayoría de las respuestas se centraron en las B, mientras que necesitaba una solución más general como se indica. Aquí hay uno:

 def _order_by_number(items): regex = re.compile(u'(.*?)(\d*)$') # pass a regex in for generality keys = {k: regex.match(k) for k in items} keys = {k: (v.groups()[0], int(v.groups()[1] or 0)) for k, v in keys.iteritems()} items.sort(key=keys.__getitem__) 

Todavía estoy buscando una llave mágica sin embargo eso dejaría las cosas en su lugar

Puede utilizar el módulo natsort :

 >>> from natsort import natsorted >>> >>> a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] >>> natsorted(a) ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

Si los elementos que se van a ordenar están todos adyacentes entre sí en la lista:

Puede usar cmp en la función sorted() lugar de en la key :

 s1=['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] def compare(a,b): if (a[0],b[0])==('B','B'): #change to whichever condition you'd like inta=int(a[1:] or 0) intb=int(b[1:] or 0) return cmp(inta,intb) #change to whichever mode of comparison you'd like else: return 0 #if one of a, b doesn't fulfill the condition, do nothing sorted(s1,cmp=compare) 

Esto supone la transitividad para el comparador, lo que no es cierto para un caso más general. Esto también es mucho más lento que usar la key , pero la ventaja es que puede tener en cuenta el contexto (en pequeña medida).

Si los elementos que se van a ordenar no son todos adyacentes entre sí en la lista:

Podría generalizar los algoritmos de ordenación de tipo de comparación marcando todos los demás elementos de la lista, y no solo los vecinos:

 s1=['11', '2', 'A', 'B', 'B11', 'B21', 'B1', 'B2', 'C', 'C11', 'C2', 'B09','C8','B19'] def cond1(a): #change this to whichever condition you'd like return a[0]=='B' def comparison(a,b): #change this to whichever type of comparison you'd like to make inta=int(a[1:] or 0) intb=int(b[1:] or 0) return cmp(inta,intb) def n2CompareSort(alist,condition,comparison): for i in xrange(len(alist)): for j in xrange(i): if condition(alist[i]) and condition(alist[j]): if comparison(alist[i],alist[j])==-1: alist[i], alist[j] = alist[j], alist[i] #in-place swap n2CompareSort(s1,cond1,comparison) 

No creo que nada de esto sea menos complicado que hacer una lista / tupla por separado, pero está “en el lugar” y deja intactos los elementos que no cumplen nuestra condición.

Puede utilizar la siguiente función de tecla. Devolverá una tupla del formulario (letter, number) si hay un número, o del formulario (letter,) si no hay número. Esto funciona desde ('A',) < ('A', 1) .

 import re a = ['A', 'B' ,'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] regex = re.compile(r'(\d+)') def order(e): num = regex.findall(e) if num: num = int(num[0]) return e[0], num return e, print(sorted(a, key=order)) >> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

Si entiendo claramente su pregunta, está intentando clasificar una matriz por dos atributos ; El alfabeto y el ‘número’ al final.

Podrías hacer algo como

 data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] data.sort(key=lambda elem: (elem[0], int(elem[1:])) 

pero como esto generaría una excepción para los elementos sin un número que los siga, podemos seguir adelante y simplemente hacer una función (¡de todos modos no deberíamos usar lambda !)

 def sortKey(elem): try: attribute = (elem[0], int(elem[1:])) except: attribute = (elem[0], 0) return attribute 

Con esta función clave de clasificación realizada, podemos ordenar el elemento en su lugar por

 data.sort(key=sortKey) 

Además, puede seguir adelante y ajustar la función sortKey para dar prioridad a ciertos alfabetos si así lo desea.

Para responder con precisión a lo que describe puede hacer esto:

 l = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2', 'D'] def custom_sort(data, c): s = next(i for i, x in enumerate(data) if x.startswith(c)) e = next((i for i, x in enumerate(data) if not x.startswith(c) and i > s), -1) return data[:s] + sorted(data[s:e], key=lambda d: int(d[1:] or -1)) + data[e:] print(custom_sort(l, "B")) 

Si tiene una clasificación completa, simplemente puede hacer esto (como responde @ Mike JS Choi pero más simple):

 output = sorted(l, key=lambda elem: (elem[0], int(elem[1:] or -1))) 

Puede usar ord () para transformar, por ejemplo, ‘B11’ en valor numérico:

 cells = ['B11', 'C1', 'A', 'B1', 'B2', 'B21', 'B22', 'C11', 'C2', 'B'] conv_cells = [] ## Transform expression in numerical value. for x, cell in enumerate(cells): val = ord(cell[0]) * (ord(cell[0]) - 65) ## Add weight to ensure respect order. if len(cell) > 1: val += int(cell[1:]) conv_cells.append((val, x)) ## List of tuple (num_val, index). ## Display result. for x in sorted(conv_cells): print(str(cells[x[1]]) + ' - ' + str(x[0])) 

Si desea clasificar con diferentes reglas para diferentes subgrupos, puede utilizar las tuplas como claves de clasificación. En este caso, los elementos se agruparían y clasificarían capa por capa: primero por primer elemento de la tupla, luego en cada subgrupo por segundo elemento de la tupla y así sucesivamente. Esto nos permite tener diferentes reglas de clasificación en diferentes subgrupos. El único límite: los elementos deben ser comparables dentro de cada grupo. Por ejemplo, no puede tener claves de tipo int y str en el mismo subgrupo, pero puede tenerlas en diferentes subgrupos.

Vamos a tratar de aplicarlo a la tarea. Prepararemos tuplas con tipos de elementos ( str , int ) para elementos B, y tuplas con ( str , str ) para todos los demás.

 def sorter(elem): letter, num = elem[0], elem[1:] if letter == 'B': return letter, int(num or 0) # hack - if we've got `''` as num, replace it with `0` else: return letter, num data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] sorted(data, key=sorter) # returns ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 

ACTUALIZAR

Si lo prefieres en una sola línea:

 data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] sorted(data, key=lambda elem: (elem[0], int(elem[1:] or 0) if elem[0]=='B' else elem[:1] # result ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

De todos modos, estas funciones clave son bastante simples, por lo que puede adaptarlas a las necesidades reales.

 import numpy as np def sort_with_prefix(list, prefix): alist = np.array(list) ix = np.where([l.startswith(prefix) for l in list]) alist[ix] = [prefix + str(n or '') for n in np.sort([int(l.split(prefix)[-1] or 0) for l in alist[ix]])] return alist.tolist() 

Por ejemplo:

 l = ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] print(sort_with_prefix(l, 'B')) >> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 

Usando solo la tecla y la condición previa de que la secuencia ya está ‘ordenada’:

 import re s = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] def subgroup_ordinate(element): # Split the sequence element values into groups and ordinal values. # use a simple regex and int() in this case m = re.search('(B)(.+)', element) if m: subgroup = m.group(1) ordinate = int(m.group(2)) else: subgroup = element ordinate = None return (subgroup, ordinate) print sorted(s, key=subgroup_ordinate) ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

La función subgroup_ordinate() hace dos cosas: identifica los grupos que deben ordenarse y también determina el número ordinal dentro de los grupos. Este ejemplo usa expresiones regulares pero la función podría ser arbitrariamente compleja. Por ejemplo, podemos cambiarlo a ur'(B|C)(.+)' Y ordenar las secuencias B y C.

 ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

Leyendo la pregunta de la generosidad cuidadosamente, observo que el requisito ‘ordena algunos valores mientras deja otros “en su lugar”‘. Definir la función de comparación para devolver 0 para los elementos que no están en subgrupos dejaría estos elementos donde estaban en la secuencia.

 s2 = ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11'] def compare((_a,a),(_b,b)): return 0 if a is None or b is None else cmp(a,b) print sorted(s, compare, subgroup_ordinate) ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11'] 
 import re from collections import OrderedDict a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] dict = OrderedDict() def get_str(item): _str = list(map(str, re.findall(r"[A-Za-z]", item))) return _str def get_digit(item): _digit = list(map(int, re.findall(r"\d+", item))) return _digit for item in a: _str = get_str(item) dict[_str[0]] = sorted([get_digit(dig) for dig in a if _str[0] in dig]) nested_result = [[("{0}{1}".format(k,v[0]) if v else k) for v in dict[k]] for k in dict.keys()] print (nested_result) # >>> [['A'], ['B', 'B1', 'B2', 'B11', 'B21', 'B22'], ['C', 'C1', 'C2', 'C11']] result = [] for k in dict.keys(): for v in dict[k]: result.append("{0}{1}".format(k,v[0]) if v else k) print (result) # >>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11'] 

Si desea ordenar un subconjunto de elementos arbitrarios mientras deja otros elementos en su lugar, puede ser útil diseñar una vista sobre la lista original. La idea de una vista en general es que es como una lente sobre la lista original, pero su modificación manipulará la lista original subyacente. Considera esta clase de ayuda:

 class SubList: def __init__(self, items, predicate): self.items = items self.indexes = [i for i in range(len(items)) if predicate(items[i])] @property def values(self): return [self.items[i] for i in self.indexes] def sort(self, key): for i, v in zip(self.indexes, sorted(self.values, key=key)): self.items[i] = v 

El constructor guarda la lista original en self.items y los índices originales en self.indexes , según lo determinado por el predicate . En sus ejemplos, la función de predicate puede ser esta:

 def predicate(item): return item.startswith('B') 

Luego, la propiedad de values es la lente sobre la lista original, devolviendo una lista de valores seleccionados de la lista original por los índices originales.

Finalmente, la función de sort usa self.values de self.values para ordenar y luego modifica la lista original.

Considere esta demostración con doctests:

 def demo(values): """ >>> demo(['X', 'b3', 'a', 'b1', 'b2']) ['X', 'b1', 'a', 'b2', 'b3'] """ def predicate(item): return item.startswith('b') sub = SubList(values, predicate) def key(item): return int(item[1:]) sub.sort(key) return values 

Observe cómo se usa SubList solo como una herramienta a través de la cual se manipulan los values entrada. Después de la llamada de sub.sort , los values se modifican, con los elementos a ordenar seleccionados por la función de predicate , y se ordenan de acuerdo con la función key , y todos los demás elementos nunca se movieron.

Usando este auxiliar de SubList con funciones de predicate y key apropiadas, puede ordenar la selección arbitraria de elementos de una lista.

 def compound_sort(input_list, natural_sort_prefixes=()): padding = '{:0>%s}' % len(max(input_list, key=len)) return sorted( input_list, key = lambda li: \ ''.join( [li for c in '_' if not li.startswith(natural_sort_prefixes)] or [c for c in li if not c.isdigit()] + \ [c for c in padding.format(li) if c.isdigit()] ) ) 

Este método de clasificación recibe:

  • input_list : la list a ordenar,
  • natural_sort_prefixes : Una string o una tuple de string s.

Los elementos de lista seleccionados por natural_sort_prefixes se ordenarán de forma natural . Los elementos que no coincidan con esos prefijos se ordenarán lexicográficamente .

Este método supone que los elementos de la lista están estructurados como uno o más caracteres no numéricos seguidos de uno o más dígitos.

Debería ser más eficaz que las soluciones que usan expresiones regulares, y no depende de bibliotecas externas.

Puedes usarlo como:

 print compound_sort(['A', 'B' , 'B11', 'B1', 'B2', 'C11', 'C2'], natural_sort_prefixes=("A","B")) # ['A', 'B', 'B1', 'B2', 'B11', 'C11', 'C2']