Est-il possible d'avoir plusieurs instructions dans une expression lambda python?

112

Je suis un novice en python qui essaie de réaliser ce qui suit:

J'ai une liste de listes:

lst = [[567,345,234],[253,465,756, 2345],[333,777,111, 555]]

Je veux mapper lst dans une autre liste contenant uniquement le deuxième plus petit nombre de chaque sous-liste. Le résultat devrait donc être:

[345, 465, 333]

Par exemple, si j'étais simplement intéressé par le plus petit nombre, je pourrais faire:

map(lambda x: min(x),lst)

J'aimerais pouvoir faire ceci:

map(lambda x: sort(x)[1],lst)

mais le tri ne s'enchaîne pas. (renvoie Aucun)

quelque chose comme ça n'est pas non plus autorisé:

map(lambda x: sort(x); x[1],lst) #hence the multiple statement question

Existe-t-il un moyen de faire cela avec map en python mais sans définir une fonction nommée ? (c'est facile avec des blocs anonymes en rubis, par exemple)

ottodidakt
la source
c'est sûr que cela devrait être possible .. peut-être dans une nouvelle version ...
ZEE
1
Vous ne pouvez pas exécuter d'instructions mais vous pouvez appeler des fonctions dans la fonction lambda, donc le hack sale impythonique lambda x: sort(x) OR x[1]fonctionnerait: Ici, le OR évalue son premier argument (valeur de retour Aucun) comme un bool (=> False), et dans ce cas OU renvoie son deuxième argument. Mais comme le disent les réponses, mieux vaut éviter lambda.
Max

Réponses:

136

Il y a plusieurs réponses différentes que je peux donner ici, de votre question spécifique à des préoccupations plus générales. Donc du plus spécifique au plus général:

Q. Pouvez-vous mettre plusieurs déclarations dans un lambda?

R. Non, mais vous n'avez pas vraiment besoin d'utiliser un lambda. Vous pouvez placer les instructions dans un deffichier. c'est à dire:

def second_lowest(l):
    l.sort()
    return l[1]

map(second_lowest, lst)

Q. Pouvez-vous obtenir le deuxième élément le plus bas d'un lambda en triant la liste?

R. Oui. Comme le souligne la réponse d'Alex , sorted()est une version de tri qui crée une nouvelle liste, plutôt que de trier sur place, et peut être chaînée. Notez que c'est probablement ce que vous devriez utiliser - c'est une mauvaise pratique pour votre carte d'avoir des effets secondaires sur la liste d'origine.

Q. Comment dois-je obtenir le deuxième élément le plus bas de chaque liste dans une séquence de listes?

A. sorted(l)[1] n'est pas vraiment le meilleur moyen pour cela. Il a une complexité O (N log (N)), alors qu'une solution O (n) existe. Cela peut être trouvé dans le module heapq.

>>> import  heapq
>>> l = [5,2,6,8,3,5]
>>> heapq.nsmallest(l, 2)
[2, 3]

Alors utilisez simplement:

map(lambda x: heapq.nsmallest(x,2)[1],  list_of_lists)

Il est également généralement considéré comme plus clair d'utiliser une compréhension de liste, ce qui évite complètement le lambda:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]
Brian
la source
3
Je ne pense pas que vous ayez raison sur la solution O (n) trouvée dans le module heapq. Tout ce que vous faites là-bas est de trier en tas la liste, qui est O (n log n), puis de trouver les plus petits éléments.
avpx
8
La documentation ( docs.python.org/2/library/heapq.html#heapq.nsmallest ) avertit en effet que l'utilisation de heapq.nsmallest () peut être moins efficace que d'utiliser simplement sorted (), donc seules les mesures peuvent dire quelle solution est le plus rapide dans votre cas. heapq.nsmallest () a cependant une complexité de O (k * log (n) + n) je pense, avec n la longueur de la liste et k le nombre de plus petits éléments que vous souhaitez extraire. O (n) pour heapifier la liste et k fois O (log (n)) pour pop k éléments. C'est mieux que O (n * log (n)), en particulier pour les petits k.
Vortexfive
2
@Vortexfive et for constant k(comme ici avec `` deuxième plus bas '') se O(k*log(n)+n)simplifie àO(n)
Duncan
5
Techniquement, vous n'avez pas non plus besoin d'un lambda pour les monoplaces, mais c'est certainement plus pratique. Espérons qu'un meilleur support lambda sera ajouté plus tard.
James
1
@avpx: Ce n'est pas un tas de choses. Nous ne maintenons qu'un tas de 2 éléments des 2 plus petits éléments vus, plutôt que de remplir la liste entière et de faire sauter tous les éléments comme le ferait un tas de tas.
user2357112 prend en charge Monica le
81

Mettre les instructions dans une liste peut simuler plusieurs instructions:

Par exemple:

lambda x: [f1(x), f2(x), f3(x), x+1]
jmkg
la source
9
Juste pour clarifier, les appels de fonction ne sont pas considérés comme des instructions en Python, ce sont des expressions. Cette liste n'est donc qu'une liste d'expressions, qui peuvent toutes l'être None.
Yawar
Je pense que cela fonctionne car lors de l'analyse dynamique de la collection transmise au lambda, l'interpréteur détecte les signatures appelables et les exécute ... vous pouvez tester avec -> lambda x: [print (x), print (x + 1), print (x +2)]
ZEE
5
Mieux encore: lambda x: [f1 (x), f2 (x)] [- 1], il retournera le résultat du calcul de la dernière expression, comme prévu.
Anton Ovsyannikov
22

Voyageur du temps ici. Si vous souhaitez généralement avoir plusieurs instructions dans un lambda, vous pouvez passer d'autres lambdas comme arguments à ce lambda.

(lambda x, f: list((y[1] for y in f(x))))(lst, lambda x: (sorted(y) for y in x))

Vous ne pouvez pas réellement avoir plusieurs instructions, mais vous pouvez simuler cela en passant des lambdas aux lambdas.

Edit: Le voyageur du temps revient! Vous pouvez également abuser du comportement des expressions booléennes (en gardant à l'esprit les règles de court-circuit et la véracité) pour enchaîner les opérations. L'utilisation de l'opérateur ternaire vous donne encore plus de puissance. Encore une fois, vous ne pouvez pas avoir plusieurs instructions , mais vous pouvez bien sûr avoir de nombreux appels de fonction. Cet exemple fait des choses arbitraires avec un tas de données, mais cela montre que vous pouvez faire des choses amusantes. Les instructions print sont des exemples de fonctions qui retournent None(tout comme la .sort()méthode) mais elles aident également à montrer ce que lambdafait.

>>> (lambda x: print(x) or x+1)(10)
10
11
>>> f = (lambda x: x[::2] if print(x) or x.sort() else print(enumerate(x[::-1]) if print(x) else filter(lambda (i, y): print((i, y)) or (i % 3 and y % 2), enumerate(x[::-1]))))
>>> from random import shuffle
>>> l = list(range(100))
>>> shuffle(l)
>>> f(l)
[84, 58, 7, 99, 17, 14, 60, 35, 12, 56, 26, 48, 55, 40, 28, 52, 31, 39, 43, 96, 64, 63, 54, 37, 79, 25, 46, 72, 10, 59, 24, 68, 23, 13, 34, 41, 94, 29, 62, 2, 50, 32, 11, 97, 98, 3, 70, 93, 1, 36, 87, 47, 20, 73, 45, 0, 65, 57, 6, 76, 16, 85, 95, 61, 4, 77, 21, 81, 82, 30, 53, 51, 42, 67, 74, 8, 15, 83, 5, 9, 78, 66, 44, 27, 19, 91, 90, 18, 49, 86, 22, 75, 71, 88, 92, 33, 89, 69, 80, 38]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
(0, 99)
(1, 98)
(2, 97)
(3, 96)
(4, 95)
(5, 94)
(6, 93)
(7, 92)
(8, 91)
(9, 90)
(10, 89)
(11, 88)
(12, 87)
(13, 86)
(14, 85)
(15, 84)
(16, 83)
(17, 82)
(18, 81)
(19, 80)
(20, 79)
(21, 78)
(22, 77)
(23, 76)
(24, 75)
(25, 74)
(26, 73)
(27, 72)
(28, 71)
(29, 70)
(30, 69)
(31, 68)
(32, 67)
(33, 66)
(34, 65)
(35, 64)
(36, 63)
(37, 62)
(38, 61)
(39, 60)
(40, 59)
(41, 58)
(42, 57)
(43, 56)
(44, 55)
(45, 54)
(46, 53)
(47, 52)
(48, 51)
(49, 50)
(50, 49)
(51, 48)
(52, 47)
(53, 46)
(54, 45)
(55, 44)
(56, 43)
(57, 42)
(58, 41)
(59, 40)
(60, 39)
(61, 38)
(62, 37)
(63, 36)
(64, 35)
(65, 34)
(66, 33)
(67, 32)
(68, 31)
(69, 30)
(70, 29)
(71, 28)
(72, 27)
(73, 26)
(74, 25)
(75, 24)
(76, 23)
(77, 22)
(78, 21)
(79, 20)
(80, 19)
(81, 18)
(82, 17)
(83, 16)
(84, 15)
(85, 14)
(86, 13)
(87, 12)
(88, 11)
(89, 10)
(90, 9)
(91, 8)
(92, 7)
(93, 6)
(94, 5)
(95, 4)
(96, 3)
(97, 2)
(98, 1)
(99, 0)
[(2, 97), (4, 95), (8, 91), (10, 89), (14, 85), (16, 83), (20, 79), (22, 77), (26, 73), (28, 71), (32, 67), (34, 65), (38, 61), (40, 59), (44, 55), (46, 53), (50, 49), (52, 47), (56, 43), (58, 41), (62, 37), (64, 35), (68, 31), (70, 29), (74, 25), (76, 23), (80, 19), (82, 17), (86, 13), (88, 11), (92, 7), (94, 5), (98, 1)]
2rs2ts
la source
7

Utilisez la fonction triée , comme ceci:

map(lambda x: sorted(x)[1],lst)
alex vasi
la source
ah.Merci. (quel nom descriptif pour la fonction!) Toujours curieux de connaître l'instruction multiple dans la partie lambda. Possible?
ottodidakt
1
Non, "les fonctions créées avec des formulaires lambda ne peuvent pas contenir d'instructions". docs.python.org/reference/expressions.html#lambda
alex vasi
1
-1: lambda et map au lieu de list comprehensions? Pas très pythonique.
nikow
@nikow [x [1] pour x en trié (liste)] - manière pythonique?
RaGa__M
@Explorer_N: ce n'est pas la liste qui doit être triée, mais les éléments de la liste (qui sont des listes) doivent être triés dans le présent problème. Ainsi , plutôt: [sorted(x)[1] for x in list_of_lists].
Max
6

Vous pouvez en fait avoir plusieurs instructions dans une expression lambda en python. Ce n'est pas tout à fait anodin mais dans votre exemple, les travaux suivants:

map(lambda x: x.sort() or x[1],lst)

Vous devez vous assurer que chaque instruction ne renvoie rien ou si elle l'enveloppe (.. et False). Le résultat est ce qui est renvoyé par la dernière évaluation.

Exemple:

>>> f = (lambda : (print(1) and False) or (print(2) and False) or (print(3) and False))
>>> f()
1
2
3
Loreno Heer
la source
5

Utilisation de begin () à partir d'ici: http://www.reddit.com/r/Python/comments/hms4z/ask_pyreddit_if_you_were_making_your_own/c1wycci

Python 3.2 (r32:88445, Mar 25 2011, 19:28:28) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> lst = [[567,345,234],[253,465,756, 2345],[333,777,111, 555]]
>>> begin = lambda *args: args[-1]
>>> list(map(lambda x: begin(x.sort(), x[1]), lst))
[345, 465, 333]
Janus Troelsen
la source
4

Un moyen Hacky de combiner plusieurs instructions en une seule instruction en python consiste à utiliser le mot-clé "et" comme opérateur de court-circuit. Ensuite, vous pouvez utiliser cette instruction unique directement dans le cadre de l'expression lambda.

Ceci est similaire à l'utilisation de "&&" comme opérateur de court-circuit dans des langages shell tels que bash.

Remarquez également: vous pouvez toujours corriger une instruction de fonction pour renvoyer une valeur vraie en encapsulant la fonction.

Exemple:

def p2(*args):
    print(*args)
    return 1 # a true value

junky = lambda x, y: p2('hi') and p2('there') and p2(x) and p2(y)

junky("a", "b")

Après réflexion, il est probablement préférable d'utiliser 'ou' au lieu de 'et' puisque de nombreuses fonctions renvoient '0' ou None en cas de succès. Ensuite, vous pouvez vous débarrasser de la fonction wrapper dans l'exemple ci-dessus:

junky = lambda x, y: print('hi') or print('there') or print(x) or print(y)

junky("a", "b")

'et' operera évalueront les expressions jusqu'à ce qu'il atteigne la première valeur de retour zéro. après quoi il court-circuite. 1 et 1 et 0 et 1 évaluent: 1 et 1 et 0, et supprime 1

'ou' operera évaluera les expressions jusqu'à ce qu'il atteigne la première valeur de retour différente de zéro. après quoi il court-circuite.

0 ou 0 ou 1 ou 0 évalue 0 ou 0 ou 1 et abandonne 0

Bill Moore
la source
3

Ou si vous voulez éviter lambda et avoir un générateur au lieu d'une liste:

(trié (col) [1] pour col dans lst)

odwl
la source
2

Permettez-moi de vous présenter un hack glorieux mais terrifiant:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Vous pouvez désormais utiliser ce LETformulaire comme tel:

map(lambda x: LET(('_', x.sort()),
                  lambda _: x[1]),
    lst)

qui donne: [345, 465, 333]

divs1210
la source
Publié à l'
divs1210
1

Vous pouvez le faire en temps O (n) en utilisant min et index au lieu d'utiliser sort ou heapq.

Créez d'abord une nouvelle liste de tout sauf la valeur minimale de la liste d'origine:

new_list = lst[:lst.index(min(lst))] + lst[lst.index(min(lst))+1:]

Ensuite, prenez la valeur min de la nouvelle liste:

second_smallest = min(new_list)

Maintenant tous ensemble dans un seul lambda:

map(lambda x: min(x[:x.index(min(x))] + x[x.index(min(x))+1:]), lst)

Oui, c'est vraiment moche, mais cela devrait être bon marché sur le plan algorithmique. Aussi, puisque certaines personnes de ce fil veulent voir les compréhensions de liste:

[min(x[:x.index(min(x))] + x[x.index(min(x))+1:]) for x in lst]
nu fanatique
la source
1

C'est exactement ce à quoi bindsert la fonction d'une Monad .

Avec la bindfonction, vous pouvez combiner plusieurs lambda en un seul lambda, chaque lambda représentant une instruction.

jhegedus
la source
1

Il existe en fait un moyen d'utiliser plusieurs instructions dans lambda. Voici ma solution:

lst = [[567,345,234],[253,465,756, 2345],[333,777,111, 555]]

x = lambda l: exec("l.sort(); return l[1]")

map(x, lst)
EternalCrafter_606
la source
le code à l'intérieur execne peut pas être introspectionné par un IDE intelligent
javadba
0

Oui. Vous pouvez le définir de cette façon, puis envelopper vos multiples expressions avec les éléments suivants:

Début du schéma:

begin = lambda * x: x [-1]

Progn Common Lisp:

progn = lambda * x: x [-1]

Anastasios
la source
0

Il existe de meilleures solutions sans utiliser la fonction lambda. Mais si nous voulons vraiment utiliser la fonction lambda, voici une solution générique pour gérer plusieurs instructions: map (lambda x: x [1] if (x.sort ()) else x [1], lst)

Vous ne vous souciez pas vraiment de ce que la déclaration renvoie.

user11166890
la source
0

Oui c'est possible. Essayez ci-dessous l'extrait de code.

x = [('human', 1), ('i', 2), ('am', 1), ('.', 1), ('love', 1), ('python', 3), ('', 1),
  ('run', 1), ('is', 2), ('robust', 1), ('hello', 1), ('spark', 2), ('to', 1), ('analysis', 2), ('on', 1), ('big', 1), ('data', 1), ('with', 1), ('analysis', 1), ('great', 1)
]

rdd_filter = rdd1_word_cnt_sum.filter(lambda x: 'python' in x or 'human' in x or 'big' in x)
rdd_filter.collect()
Ravi
la source
-1

Je vais vous donner une autre solution, faites que votre lambda appelle une fonction.

def multiple_statements(x, y):
    print('hi')
    print('there')
    print(x)
    print(y)
    return 1

junky = lambda x, y: multiple_statements(x, y)

junky('a', 'b');
Bill Moore
la source
7
Mieux encore: junky = multiple_statements.
Solomon Ucko