¿Es posible tener varias declaraciones en una expresión lambda de Python?

112

Soy un novato en Python que intenta lograr lo siguiente:

Tengo una lista de listas:

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

Quiero mapear lst en otra lista que contenga solo el segundo número más pequeño de cada sublista. Entonces el resultado debería ser:

[345, 465, 333]

Por ejemplo, si solo estuviera interesado en el número más pequeño, podría hacer:

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

Ojalá pudiera hacer esto:

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

pero el género no encadena. (devuelve Ninguno)

tampoco se permite algo como esto:

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

¿Hay alguna manera de hacer esto con map en python pero sin definir una función con nombre ? (es fácil con bloques anónimos en ruby, por ejemplo)

ottodidakt
fuente
seguro que esto debería ser posible ... tal vez en una nueva versión ...
ZEE
1
No puede ejecutar declaraciones, pero puede llamar a funciones en la función lambda, por lo que el truco sucio no pitónico lambda x: sort(x) OR x[1]funcionaría: aquí el OR evalúa su primer argumento (valor de retorno Ninguno) como un bool (=> Falso), y en ese caso OR devuelve su segundo argumento. Pero como dicen las respuestas, es mejor evitar lambda.
Max

Respuestas:

136

Hay varias respuestas diferentes que puedo dar aquí, desde su pregunta específica hasta preocupaciones más generales. Entonces, de lo más específico a lo más general:

P. ¿Puede poner varias declaraciones en una lambda?

R. No. Pero en realidad no es necesario utilizar una lambda. Puede poner las declaraciones en un def. es decir:

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

map(second_lowest, lst)

P. ¿Puede obtener el segundo elemento más bajo de una lambda ordenando la lista?

R. Sí. Como señala la respuesta de Alex, sorted()es una versión de sort que crea una nueva lista, en lugar de ordenar en el lugar, y se puede encadenar. Tenga en cuenta que esto es probablemente lo que debería usar; es una mala práctica que su mapa tenga efectos secundarios en la lista original.

P. ¿Cómo debo obtener el segundo elemento más bajo de cada lista en una secuencia de listas?

A. sorted(l)[1] no es realmente la mejor manera de hacerlo. Tiene una complejidad O (N log (N)), mientras que existe una solución O (n). Esto se puede encontrar en el módulo heapq.

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

Así que solo usa:

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

También se suele considerar más claro usar una lista de comprensión, que evita la lambda por completo:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]
Brian
fuente
3
No creo que tenga razón sobre la solución O (n) que se encuentra en el módulo heapq. Todo lo que está haciendo allí es ordenar en montón la lista, que es O (n log n) y luego encontrar los elementos más pequeños.
avpx
8
La documentación ( docs.python.org/2/library/heapq.html#heapq.nsmallest ) advierte de hecho que usar heapq.nsmallest () puede ser menos eficiente que usar sorted (), por lo que solo las mediciones pueden decir qué solución es más rápido en tu caso. heapq.nsmallest (), sin embargo, tiene una complejidad de O (k * log (n) + n), creo, con n la longitud de la lista yk el número de elementos más pequeños que desea extraer. O (n) para agrupar la lista yk veces O (log (n)) para hacer estallar k elementos. Esto es mejor que O (n * log (n)), especialmente para k pequeño.
Vortexfive
2
@Vortexfive y for constante k(como aquí con 'segundo más bajo') se O(k*log(n)+n)simplifica aO(n)
Duncan
5
Técnicamente, tampoco necesitas una lambda para frases ingeniosas, pero ciertamente es más conveniente. Es de esperar que más adelante se agregue un mejor soporte lambda.
James
1
@avpx: no es un heapsort. Solo mantenemos un montón de 2 elementos de los 2 elementos más pequeños vistos, en lugar de acumular toda la lista y hacer estallar todos los elementos como lo haría un heapsort.
user2357112 apoya a Monica
81

Poner las declaraciones en una lista puede simular varias declaraciones:

P.ej:

lambda x: [f1(x), f2(x), f3(x), x+1]
jmkg
fuente
9
Solo para aclarar, las llamadas a funciones no se consideran declaraciones en Python, son expresiones. Entonces esta lista es solo una lista de expresiones, que pueden ser todas None.
Yawar
Creo que esto funciona porque al analizar dinámicamente la colección pasada a la lambda, el intérprete detecta firmas invocables y las ejecuta ... puedes probar con -> lambda x: [print (x), print (x + 1), print (x +2)]
ZEE
5
Aún mejor: lambda x: [f1 (x), f2 (x)] [- 1], devolverá el resultado del cálculo de la última expresión, como probablemente se esperaba.
Anton Ovsyannikov
22

Viajero del tiempo aquí. Si generalmente desea tener varias declaraciones dentro de una lambda, puede pasar otras lambdas como argumentos a esa lambda.

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

En realidad, no puede tener varias declaraciones, pero puede simular eso pasando lambdas a lambdas.

Editar: ¡ El viajero del tiempo regresa! También puede abusar del comportamiento de las expresiones booleanas (teniendo en cuenta las reglas de cortocircuito y la veracidad) para encadenar operaciones. El uso del operador ternario le brinda aún más potencia. Una vez más, no puede tener varias declaraciones , pero, por supuesto, puede tener muchas llamadas a funciones. Este ejemplo hace basura arbitraria con un montón de datos, pero muestra que puedes hacer cosas divertidas. Las declaraciones de impresión son ejemplos de funciones que regresan None(al igual que el .sort()método) pero también ayudan a mostrar lo que lambdaestá haciendo.

>>> (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
fuente
7

Utilice una función ordenada , como esta:

map(lambda x: sorted(x)[1],lst)
alex vasi
fuente
ah.Gracias. (¡Qué nombre tan descriptivo para la función!) Todavía tengo curiosidad por la declaración múltiple dentro de la parte lambda. ¿Posible?
ottodidakt
1
No, "las funciones creadas con formularios lambda no pueden contener declaraciones". docs.python.org/reference/expressions.html#lambda
alex vasi
1
-1: ¿lambda y mapa en lugar de listas por comprensión? No muy pitónico.
nikow
@nikow [x [1] para x en ordenado (lista)] - forma pitónica?
RaGa__M
@Explorer_N: no se debe ordenar la lista, pero los elementos de la lista (que son listas) se deben ordenar en el problema actual. Así que en lugar: [sorted(x)[1] for x in list_of_lists].
Max
6

De hecho, puede tener varias declaraciones en una expresión lambda en Python. No es del todo trivial, pero en su ejemplo, lo siguiente funciona:

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

Debe asegurarse de que cada declaración no devuelva nada o si lo envuelve (.. y False). El resultado es lo que devuelve la última evaluación.

Ejemplo:

>>> f = (lambda : (print(1) and False) or (print(2) and False) or (print(3) and False))
>>> f()
1
2
3
Loreno Heer
fuente
4

Una forma de Hacky de combinar varias declaraciones en una sola declaración en Python es usar la palabra clave "y" como operador de cortocircuito. Luego, puede usar esta única declaración directamente como parte de la expresión lambda.

Esto es similar a usar "&&" como operador de cortocircuito en lenguajes de shell como bash.

También tenga en cuenta: siempre puede corregir una declaración de función para devolver un valor verdadero envolviendo la función.

Ejemplo:

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")

Pensándolo bien, probablemente sea mejor usar 'o' en lugar de 'y' ya que muchas funciones devuelven '0' o Ninguno en caso de éxito. Entonces puede deshacerse de la función de contenedor en el ejemplo anterior:

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

junky("a", "b")

'y' operar evaluarán las expresiones hasta que llegue al primer valor de retorno cero. después de lo cual se cortocircuita. 1 y 1 y 0 y 1 evalúa: 1 y 1 y 0, y elimina 1

'u' operar evaluará las expresiones hasta que llegue al primer valor de retorno distinto de cero. después de lo cual se cortocircuita.

0 o 0 o 1 o 0 evalúa 0 o 0 o 1, y elimina 0

Bill Moore
fuente
3

O si desea evitar lambda y tener un generador en lugar de una lista:

(ordenado (col) [1] para col en lst)

odwl
fuente
2

Permítanme presentarles un truco glorioso pero aterrador:

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)

Ahora puede utilizar este LETformulario como tal:

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

lo que da: [345, 465, 333]

divs1210
fuente
Publicado originalmente aquí: gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
1

Puede hacerlo en O (n) time usando min e index en lugar de usar sort o heapq.

Primero cree una nueva lista de todo excepto el valor mínimo de la lista original:

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

Luego toma el valor mínimo de la nueva lista:

second_smallest = min(new_list)

Ahora todos juntos en una sola lambda:

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

Sí, es realmente feo, pero debería ser algorítmicamente barato. Además, dado que algunas personas en este hilo quieren ver listas por comprensión:

[min(x[:x.index(min(x))] + x[x.index(min(x))+1:]) for x in lst]
desnudofanático
fuente
1

Esto es exactamente para lo que se utiliza la bindfunción en una mónada .

Con la bindfunción puede combinar múltiples lambda en una lambda, cada lambda representa una declaración.

jhegedus
fuente
1

De hecho, existe una forma de utilizar varias declaraciones en lambda. Esta es mi solución:

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
fuente
el código interno execno puede ser introspectado por un IDE inteligente
javadba
0

Si. Puede definirlo de esta manera y luego envolver sus múltiples expresiones con lo siguiente:

Comienzo del esquema:

comenzar = lambda * x: x [-1]

Pronóstico común de Lisp:

progn = lambda * x: x [-1]

Anastasios
fuente
0

Hay mejores soluciones sin usar la función lambda. Pero si realmente queremos usar la función lambda, aquí hay una solución genérica para lidiar con múltiples declaraciones: map (lambda x: x [1] if (x.sort ()) else x [1], lst)

Realmente no te importa lo que devuelve la declaración.

usuario11166890
fuente
0

Sí, es posible. Pruebe el siguiente fragmento de código.

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
fuente
-1

Te daré otra solución, haz que tu lambda invoque una función.

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
fuente
7
Aún mejor: junky = multiple_statements.
Solomon Ucko