Je souhaite trier la liste ci-dessous d'abord par le nombre, puis par le texte.
lst = ['b-3', 'a-2', 'c-4', 'd-2']
# result:
# ['a-2', 'd-2', 'b-3', 'c-4']
Tentative 1
res = sorted(lst, key=lambda x: (int(x.split('-')[1]), x.split('-')[0]))
Cela ne me convenait pas, car il fallait scinder une chaîne deux fois pour extraire les composants pertinents.
Tentative 2
Je suis venu avec la solution ci-dessous. Mais j'espère qu'il existe une solution plus succincte via les instructions Pythonic lambda
.
def sorter_func(x):
text, num = x.split('-')
return int(num), text
res = sorted(lst, key=sorter_func)
J'ai examiné Comprendre le comportement de la fonction lambda imbriquée en python mais je n'ai pas pu adapter cette solution directement. Existe-t-il un moyen plus succinct de réécrire le code ci-dessus?
Dans presque tous les cas, j'irais simplement avec votre deuxième tentative. Il est lisible et concis (je préférerais trois lignes simples à une ligne compliquée à chaque fois!) - même si le nom de la fonction pourrait être plus descriptif. Mais si vous l'utilisez comme fonction locale, cela n'aura pas beaucoup d'importance.
N'oubliez pas non plus que Python utilise une fonction key
name__, et non une cmp
(fonction de comparaison). Par conséquent, pour trier une variable itérative de longueur n
name__, la fonction key
est appelée exactement n
fois, mais le tri effectue généralement des comparaisons O(n * log(n))
. Ainsi, chaque fois que votre fonction-clé a une complexité algorithmique de O(1)
, la surcharge de l'appel de la fonction-clé n'aura pas d'importance (beaucoup). C'est parce que:
O(n*log(n)) + O(n) == O(n*log(n))
Il y a une exception et c'est le meilleur cas pour Pythons sort
name__: Dans le meilleur des cas, sort
ne fait que O(n)
comparaisons mais cela ne se produit que si l'itérable est déjà trié (ou presque). Si Python avait une fonction de comparaison (et dans Python 2, il y en avait vraiment une), les facteurs constants de la fonction seraient beaucoup plus significatifs car ils s'appelleraient O(n * log(n))
fois (appelés une fois pour chaque comparaison).
Donc, ne vous inquiétez pas d'être plus concis ou de le rendre beaucoup plus rapide (sauf lorsque vous pouvez réduire le big-O sans introduire de trop grands facteurs constants - alors vous devriez y aller!), La première préoccupation doit être la lisibilité. Donc vous devriez vraiment not faire n'importe quel lambda
name__s imbriqué ou n'importe quelle autre construction fantaisie (sauf peut-être comme exercice).
Longue histoire courte, utilisez simplement votre # 2:
def sorter_func(x):
text, num = x.split('-')
return int(num), text
res = sorted(lst, key=sorter_func)
A propos, c'est aussi la plus rapide de toutes les approches proposées (bien que la différence ne soit pas grande):
Résumé: C'est lisible et rapide !
Code pour reproduire le repère. Il faut que simple_benchmark
soit installé pour que cela fonctionne (Avertissement: c'est ma propre bibliothèque), mais il existe probablement des cadres équivalents pour effectuer ce type de tâche, mais je le connais tout simplement:
# My specs: Windows 10, Python 3.6.6 (conda)
import toolz
import iteration_utilities as it
def approach_jpp_1(lst):
return sorted(lst, key=lambda x: (int(x.split('-')[1]), x.split('-')[0]))
def approach_jpp_2(lst):
def sorter_func(x):
text, num = x.split('-')
return int(num), text
return sorted(lst, key=sorter_func)
def jpp_nested_lambda(lst):
return sorted(lst, key=lambda x: (lambda y: (int(y[1]), y[0]))(x.split('-')))
def toolz_compose(lst):
return sorted(lst, key=toolz.compose(lambda x: (int(x[1]), x[0]), lambda x: x.split('-')))
def AshwiniChaudhary_list_comprehension(lst):
return sorted(lst, key=lambda x: [(int(num), text) for text, num in [x.split('-')]])
def AshwiniChaudhary_next(lst):
return sorted(lst, key=lambda x: next((int(num), text) for text, num in [x.split('-')]))
def PaulCornelius(lst):
return sorted(lst, key=lambda x: Tuple(f(a) for f, a in Zip((int, str), reversed(x.split('-')))))
def JeanFrançoisFabre(lst):
return sorted(lst, key=lambda s : [x if i else int(x) for i,x in enumerate(reversed(s.split("-")))])
def iteration_utilities_chained(lst):
return sorted(lst, key=it.chained(lambda x: x.split('-'), lambda x: (int(x[1]), x[0])))
from simple_benchmark import benchmark
import random
import string
funcs = [
approach_jpp_1, approach_jpp_2, jpp_nested_lambda, toolz_compose, AshwiniChaudhary_list_comprehension,
AshwiniChaudhary_next, PaulCornelius, JeanFrançoisFabre, iteration_utilities_chained
]
arguments = {2**i: ['-'.join([random.choice(string.ascii_lowercase),
str(random.randint(0, 2**(i-1)))])
for _ in range(2**i)]
for i in range(3, 15)}
b = benchmark(funcs, arguments, 'list size')
%matplotlib notebook
b.plot_difference_percentage(relative_to=approach_jpp_2)
J'ai pris la liberté d'inclure une approche de composition de fonction de l'une de mes propres bibliothèques iteration_utilities.chained
:
from iteration_utilities import chained
sorted(lst, key=chained(lambda x: x.split('-'), lambda x: (int(x[1]), x[0])))
C'est assez rapide (2e ou 3e place) mais toujours plus lent que d'utiliser votre propre fonction.
Notez que la surcharge key
serait plus importante si vous utilisiez une fonction ayant une complexité algorithmique O(n)
(ou supérieure), par exemple min
ou max
name__. Alors les facteurs constants de la fonction-clé seraient plus significatifs!
Il y a 2 points à noter:
lambda
imbriquée, car la composition de fonctions ne fait pas partie de la bibliothèque standard (voir la remarque n ° 1). Ce que vous pouvez faire facilement est d’avoir une fonction lambda
retourne le résultat de la fonction une autrelambda
.Par conséquent, la réponse correcte peut être trouvée dans Lambda à l'intérieur de lambda .
Pour votre problème spécifique, vous pouvez utiliser:
res = sorted(lst, key=lambda x: (lambda y: (int(y[1]), y[0]))(x.split('-')))
Rappelez-vous que lambda
est juste une fonction. Vous pouvez l'appeler immédiatement après l'avoir défini, même sur la même ligne.
Note n ° 1 : La 3ème partie toolz
library permet la composition:
from toolz import compose
res = sorted(lst, key=compose(lambda x: (int(x[1]), x[0]), lambda x: x.split('-')))
Note n ° 2 : Comme le souligne @chepner, l’insuffisance de cette solution (appels de fonction répétés) est l’une des raisons pour lesquelles PEP-572 est envisagé.
Nous pouvons placer la liste renvoyée par split('-')
sous une autre liste, puis utiliser une boucle pour la gérer:
# Using list-comprehension
>>> sorted(lst, key=lambda x: [(int(num), text) for text, num in [x.split('-')]])
['a-2', 'd-2', 'b-3', 'c-4']
# Using next()
>>> sorted(lst, key=lambda x: next((int(num), text) for text, num in [x.split('-')]))
['a-2', 'd-2', 'b-3', 'c-4']
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = sorted(lst, key=lambda x: Tuple(f(a) for f, a in Zip((int, str), reversed(x.split('-')))))
print(res)
['a-2', 'd-2', 'b-3', 'c-4']
vous pouvez convertir en entier uniquement si l'index de l'élément est 0 (lors de l'inversion de la liste fractionnée). Le seul objet créé (à part le résultat de split
) est la liste à 2 éléments utilisée pour la comparaison. Les autres ne sont que des itérateurs.
sorted(lst,key = lambda s : [x if i else int(x) for i,x in enumerate(reversed(s.split("-")))])
En passant, le jeton -
n’est pas particulièrement intéressant lorsque des nombres sont impliqués, car il complique l’utilisation des nombres négatifs (mais peut être résolu avec s.split("-",1)
En général, avec FOP ( programmation fonctionnelle ), vous pouvez tout mettre dans une doublure et imbriquer des lambdas dans une doublure, mais c’est en général une mauvaise étiquette, puisqu’après 2 fonctions d’emboîtement, tout devient assez illisible.
La meilleure façon d’aborder ce type de problème est de le scinder en plusieurs étapes:
1: fractionnement de chaîne en Tuple
:
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: Tuple( str_x.split('-') ) , lst)
2: trier les éléments comme vous le souhaitez:
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: Tuple( str_x.split('-') ) , lst)
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) )
Puisque nous avons scindé la chaîne en tuple, il retournera un objet de carte qui sera représenté sous forme de liste de tuples. Alors maintenant, la 3ème étape est optionnelle:
3: Représenter les données comme vous l'avez demandé:
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: Tuple( str_x.split('-') ) , lst)
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) )
res = map( '-'.join, res )
Maintenant, gardez à l’esprit que lambda nesting
pourrait produire une solution à une couche unique et que vous pouvez réellement intégrer un type de lambda à imbrication non discrète, comme suit:
a = ['b-3', 'a-2', 'c-4', 'd-2']
resa = map( lambda x: x.split('-'), a)
resa = map( lambda x: ( int(x[1]),x[0]) , a)
# resa can be written as this, but you must be sure about type you are passing to lambda
resa = map( lambda x: Tuple( map( lambda y: int(y) is y.isdigit() else y , x.split('-') ) , a)
Mais comme vous pouvez le voir, le contenu de list a
ne contient que deux types de chaînes séparés par '-'
,lambda
function va générer une erreur et vous aurez du mal à comprendre ce qui se passe.
En fin de compte, je voudrais vous montrer plusieurs manières d’écrire le programme de la 3ème étape:
1:
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( '-'.join,\
sorted(\
map( lambda str_x: Tuple( str_x.split('-') ) , lst),\
key=lambda x: ( int(x[1]), x[0] )\
)\
)
2:
lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( '-'.join,\
sorted( map( lambda str_x: Tuple( str_x.split('-') ) , lst),\
key=lambda x: Tuple( reversed( Tuple(\
map( lambda y: int(y) if y.isdigit() else y ,x )\
)))\
)\
) # map isn't reversible
3:
res = sorted( lst,\
key=lambda x:\
Tuple(reversed(\
Tuple( \
map( lambda y: int(y) if y.isdigit() else y , x.split('-') )\
)\
))\
)
Vous pouvez donc voir comment tout cela peut devenir très compliqué et incompréhensible. Lorsque je lis mon code ou celui de quelqu'un d'autre, j'aime souvent voir cette version:
res = map( lambda str_x: Tuple( str_x.split('-') ) , lst) # splitting string
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) ) # sorting for each element of splitted string
res = map( '-'.join, res ) # rejoining string
C'est tout de moi. S'amuser. J'ai testé tout le code dans py 3.6
.
PS En général, vous avez deux manières d'approcher lambda functions
:
mult = lambda x: x*2
mu_add= lambda x: mult(x)+x #calling lambda from lambda
Cette méthode est utile pour les FOP typiques, où vous avez des données constantes et vous devez manipuler chaque élément de ces données. Mais si vous devez résoudre list,Tuple,string,dict
danslambda
ce type d'opération n'est pas très utile, car si l'un de ces types container/wrapper
est présent, le type de données des éléments dans les conteneurs devient discutable. Il nous faudrait donc monter un niveau d’abstraction et déterminer comment manipuler les données en fonction de leur type.
mult_i = lambda x: x*2 if isinstance(x,int) else 2 # some ternary operator to make our life easier by putting if statement in lambda
Vous pouvez maintenant utiliser un autre type de fonction lambda
:
int_str = lambda x: ( lambda y: str(y) )(x)*x # a bit of complex, right?
# let me break it down.
#all this could be written as:
str_i = lambda x: str(x)
int_str = lambda x: str_i(x)*x
## we can separate another function inside function with ()
##because they can exclude interpreter to look at it first, then do the multiplication
# ( lambda x: str(x)) with this we've separated it as new definition of function
# ( lambda x: str(x) )(i) we called it and passed it i as argument.
Certaines personnes appellent ce type de syntaxe "lambdas" imbriqué, je l'appelle indiscret puisque vous pouvez tout voir.
Et vous pouvez utiliser une assignation lambda récursive:
def rec_lambda( data, *arg_lambda ):
# filtering all parts of lambda functions parsed as arguments
arg_lambda = [ x for x in arg_lambda if type(x).__== 'function' ]
# implementing first function in line
data = arg_lambda[0](data)
if arg_lambda[1:]: # if there are still elements in arg_lambda
return rec_lambda( data, *arg_lambda[1:] ) #call rec_lambda
else: # if arg_lambda is empty or []
return data # returns data
#where you can use it like this
a = rec_lambda( 'a', lambda x: x*2, str.upper, lambda x: (x,x), '-'.join)
>>> 'AA-AA'
lst = ['b-3', 'a-2', 'c-4', 'd-2']
def xform(l):
return list(map(lambda x: x[1] + '-' + x[0], list(map(lambda x: x.split('-'), lst))))
lst = sorted(xform(lst))
print(xform(lst))
Voir ici Je pense que @jpp a une meilleure solution, mais un petit casse-tête amusant :-)