2010-11-02 13 views
8

Se supone que debo tomar una lista de palabras y ordenarla, excepto que necesito agrupar todas las cadenas que comiencen con 'x' primero.¿Una forma más pitónica de escribir esta expresión?

Esto es lo que tengo:

list_1 = [] 
list_2 = [] 

for word in words: 
    list_1.append(word) if word[0] == 'x' else list_2.append(word) 

return sorted(list_1) + sorted(list_2) 

Pero tengo la sensación de que hay una manera mucho más elegante de hacer esto ...

EDITAR

Ejemplo: ['mix', 'xyz', 'apple', 'xanadu', 'aardvark'] rendimientos ['xanadu', 'xyz', 'aardvark', 'apple', 'mix'].

Respuesta

41
>>> words = ['xoo', 'dsd', 'xdd'] 
>>> sorted(words, key=lambda x: (x[0] != 'x', x)) 
['xdd', 'xoo', 'dsd'] 

Explicación: la tecla de función devuelve un par (tupla). El primer elemento es False o True, dependiendo de si el primer carácter en la cadena es 'x'. False ordena antes de True, por lo que las cadenas que comiencen con 'x' serán las primeras en la salida ordenada. El segundo elemento de la tupla se usará para comparar dos elementos que son iguales en el primer elemento, por lo que todas las cadenas que comiencen con 'x' se ordenarán entre sí, y todas las cadenas que no comiencen con 'x' se ordenarán entre sí.

+1

+1, me tomó un momento comprenderlo. –

+0

No entiendo por qué esto funciona. ¿Es cierto siempre mayor que x? – helpermethod

+1

@Helper: compara tuplas, cuyo primer elemento es 'False' o' True'. De esta forma, todas las tuplas tienen 'False' como primer elemento que precede a todas las tuplas que tienen' True' como primer elemento. Cuando los primeros elementos son iguales, la comparación se realiza en función del segundo elemento, que en este caso también es de tipo alfabético estándar. – SilentGhost

6
words = ['xoo', 'dsd', 'xdd'] 
list1 = [word for word in words if word[0] == 'x'] 
list2 = [word for word in words if word[0] != 'x'] 
2
words = ['xoo', 'dsd', 'xdd'] 
list1=filter(lambda word:word[0]=='x',words) 
list2=filter(lambda word:word[0]!='x',words) 
1
>>> x = ['abc', 'xyz', 'bcd', 'xabc'] 
>>> y = [ele for ele in x if ele.startswith('x')] 
>>> y 
['xyz', 'xabc'] 
>>> z = [ele for ele in x if not ele.startswith('x')] 
>>> z 
['abc', 'bcd'] 
9

Primero: deja de decir "pythonic" cuando quieres decir "clean". Es solo una palabra de moda cursi.

No use expresiones ternarias como esa; está destinado a ser usado como parte de una expresión, no como control de flujo. Esto es más limpio:

for word in words: 
    if word[0] == 'x': 
     list_1.append(word) 
    else: 
     list_2.append(word) 

se puede mejorar un poco más - el uso de expresiones como terniary esto está muy bien:

for word in words: 
    target = list_1 if word[0] == 'x' else list_2 
    target.append(word) 

Si words es un contenedor y no un iterador, que puede usar:

list_1 = [word for word in words if word[0] == 'x'] 
list_2 = [word for word in words if word[0] != 'x'] 

Por último, podemos desechar todo el asunto, y en lugar de utilizar dos tipos:

result = sorted(words) 
result = sorted(result, key=lambda word: word[0] != 'x') 

que primeros tipo normalmente, a continuación, se utiliza la propiedad estable de tipo Python para mover palabras que comienzan con "x" en la parte delantera sin cambiar de otro modo el pedido.

+0

Por cierto, aunque carece de explicación, la versión de @ SilentGhost es más rápida y más limpia que el código final. (Prefiero mi respuesta porque creo que es más instructiva, pero por supuesto que soy parcial.) –

+7

Sobre la palabra "pitónico": hay una discusión interesante en los comentarios aquí: http://nedbatchelder.com/blog/201011/ pythonic.html –

+1

Creo que el uso presentado de la expresión ternaria es perfectamente válido. uno podría incluso escribir '(list_1 if word [0] == 'x' else list_2) .append (word)' que es más claro. – flow

2

Para volver a enviar SilenGhosts variación código (no dude en copiar, SilentGhost) como código no registro del comando pronta

notinorder = ['mix', 'xyz', '', 'apple', 'xanadu', 'aardvark'] 
print sorted(notinorder, key = lambda x: (not x.startswith('x'), x)) 
+0

Me parece que los 'startswith' y' endswith' son más útiles cuando se prueban contra un conjunto de condiciones (aceptan tupla) o cuando la longitud del prefijo no está predefinida. – SilentGhost

+0

La ventaja es que puedo lidiar con '' sin corregir como x o x [0]! = 'X', el rendimiento no lo sé, pero la legibilidad cuenta ... Su solución es una especie de zip/descomprimir automático para clasificar por intérprete, cosas lindas! Podría generalizar ... Gracias por recordar el punto de tupla, he visto utilizar la tupla con una operación de cuerda, ¡pero olvidé cuál! –

0

Más lo largo de las líneas de su solución original:

 
l1=[] 
l2=[] 
for w in sorted(words): 
    (l1 if w[0] == 'x' else l2).append(w) 
l1.extend(l2) 
return l1 
5

Debe tenerse en cuenta que se agregó sorted en Python 2.4. Si desea una versión más corta que sea un poco más limpia y un poco más compatible con versiones anteriores, puede utilizar alternativamente la funcionalidad .sort() directamente en list.También se debe tener en cuenta que las cadenas vacías arrojarán una excepción al usar la sintaxis de indexación de matriz de estilo x[0] en este caso (como lo han hecho muchos ejemplos)..startswith() should be used instead, as is properly used in Tony Veijalainen's answer.

>>> words = ['mix', 'xyz', '', 'apple', 'xanadu', 'aardvark'] 
>>> words.sort(key=lambda x: (not x.startswith('x'), x)) 
>>> words 
['xanadu', 'xyz', '', 'aardvark', 'apple', 'mix'] 

La única desventaja es que estás mutando el objeto dado. Esto se puede remediar rebanando la lista de antemano.

>>> words = ['mix', 'xyz', '', 'apple', 'xanadu', 'aardvark'] 
>>> new_words = words[:] 
>>> new_words.sort(key=lambda x: (not x.startswith('x'), x)) 
>>> new_words 
['xanadu', 'xyz', '', 'aardvark', 'apple', 'mix'] 
>>> words 
['mix', 'xyz', '', 'apple', 'xanadu', 'aardvark'] 
Cuestiones relacionadas