web-dev-qa-db-fra.com

Instructions lambda imbriquées lors du tri des listes

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?

13
jpp

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 keyname__, et non une cmp(fonction de comparaison). Par conséquent, pour trier une variable itérative de longueur nname__, la fonction keyest appelée exactement nfois, 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 sortname__: Dans le meilleur des cas, sortne 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 lambdaname__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):

 enter image description here

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 keyserait plus importante si vous utilisiez une fonction ayant une complexité algorithmique O(n) (ou supérieure), par exemple minou maxname__. Alors les facteurs constants de la fonction-clé seraient plus significatifs!

2
MSeifert

Il y a 2 points à noter:

  • Les réponses à une ligne ne sont pas nécessairement meilleures. L'utilisation d'une fonction nommée rendra votre code plus facile à lire.
  • Vous êtes probablement pas à la recherche d'une instruction 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 lambdaretourne 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é.

17
jpp

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']
6
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']
2
Paul Cornelius

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)

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 '-',lambdafunction 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 danslambdace 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' 
0
Danilo
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 :-)

0
JGFMK