È possibile avere più istruzioni in un'espressione lambda python?


112

Sono un principiante di Python che cerca di ottenere quanto segue:

Ho un elenco di elenchi:

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

Voglio mappare lst in un altro elenco contenente solo il secondo numero più piccolo da ogni sottolista. Quindi il risultato dovrebbe essere:

[345, 465, 333]

Ad esempio, se fossi interessato solo al numero più piccolo, potrei fare:

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

Vorrei poterlo fare:

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

ma l'ordinamento non concatena. (restituisce Nessuno)

né è permesso qualcosa di simile:

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

C'è un modo per farlo con map in Python ma senza definire una funzione denominata ? (è facile con blocchi anonimi in ruby, per esempio)


di sicuro questo dovrebbe essere possibile .. forse in una nuova versione ...
ZEE

1
Non puoi eseguire istruzioni ma puoi chiamare funzioni nella funzione lambda, quindi il dirty hack lambda x: sort(x) OR x[1]non pitonico funzionerebbe: qui l'OR valuta il suo primo argomento (valore di ritorno None) come bool (=> False), e in quel caso OR restituisce il suo secondo argomento. Ma come dicono le risposte, è meglio evitare lambda.
Max

Risposte:


136

Ci sono molte risposte differenti che posso dare qui, dalla tua domanda specifica a preoccupazioni più generali. Quindi dal più specifico al più generale:

D. Puoi inserire più istruzioni in un lambda?

R. No. Ma in realtà non è necessario utilizzare un lambda. Puoi invece mettere le dichiarazioni in un file def. vale a dire:

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

map(second_lowest, lst)

D. Puoi ottenere il secondo elemento più basso da un lambda ordinando l'elenco?

R. Sì. Come sottolinea la risposta di alex , sorted()è una versione di sort che crea un nuovo elenco, piuttosto che l'ordinamento sul posto, e può essere concatenato. Nota che questo è probabilmente quello che dovresti usare: è una cattiva pratica che la tua mappa abbia effetti collaterali sull'elenco originale.

D. Come dovrei ottenere il secondo elemento più basso da ogni elenco in una sequenza di elenchi?

A. sorted(l)[1] non è in realtà il modo migliore per questo. Ha complessità O (N log (N)), mentre esiste una soluzione O (n). Questo può essere trovato nel modulo heapq.

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

Quindi usa:

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

Di solito è anche considerato più chiaro utilizzare una comprensione dell'elenco, che evita del tutto il lambda:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]

3
Non credo che tu abbia ragione sulla soluzione O (n) trovata nel modulo heapq. Tutto quello che stai facendo lì è ordinare la lista, che è O (n log n) e quindi trovare gli elementi più piccoli.
avpx

8
La documentazione ( docs.python.org/2/library/heapq.html#heapq.nsmallest ) avverte effettivamente che l'uso di heapq.nsmallest () potrebbe essere meno efficiente del semplice uso di Sort (), quindi solo le misurazioni possono dire quale soluzione è più veloce nel tuo caso. heapq.nsmallest () ha tuttavia una complessità di O (k * log (n) + n) penso, con n la lunghezza della lista e k il numero di elementi più piccoli che desideri estrarre. O (n) per heapify l'elenco ek volte O (log (n)) per pop k elementi. Questo è meglio di O (n * log (n)), specialmente per k piccoli.
Vortexfive

2
@Vortexfive e per costante k(come qui con il 'secondo più basso') si O(k*log(n)+n)semplifica inO(n)
Duncan

5
Tecnicamente non hai nemmeno bisogno di un lambda per le battute, ma è sicuramente più conveniente. Si spera che un supporto lambda migliore venga aggiunto in seguito.
James

1
@avpx: non è un mucchio. Manteniamo solo un mucchio di 2 elementi dei 2 elementi più piccoli visti, piuttosto che accumulare l'intero elenco e far scoppiare tutti gli elementi come farebbe un mucchio.
user2357112 supporta Monica

81

Mettere le dichiarazioni in un elenco può simulare più dichiarazioni:

Per esempio:

lambda x: [f1(x), f2(x), f3(x), x+1]

9
Giusto per chiarire, le chiamate di funzione non sono considerate istruzioni in Python, sono espressioni. Quindi questo elenco è solo un elenco di espressioni, che possono essere tutte None.
Yawar

Penso che funzioni perché quando analizza dinamicamente la raccolta passata a lambda l'interprete rileva le firme richiamabili e le esegue ... puoi testare con -> lambda x: [print (x), print (x + 1), print (x +2)]
ZEE

5
Ancora meglio: lambda x: [f1 (x), f2 (x)] [- 1], restituirà il risultato del calcolo dall'ultima espressione, come probabilmente previsto.
Anton Ovsyannikov,

22

Viaggiatore del tempo qui. Se in genere si desidera avere più istruzioni all'interno di un lambda, è possibile passare altri lambda come argomenti a quel lambda.

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

In realtà non puoi avere più istruzioni, ma puoi simularlo passando lambda a lambda.

Modifica: il viaggiatore del tempo ritorna! Puoi anche abusare del comportamento delle espressioni booleane (tenendo presenti le regole di cortocircuito e la veridicità) per concatenare le operazioni. Usare l'operatore ternario ti dà ancora più potenza. Di nuovo, non puoi avere più istruzioni , ma puoi ovviamente avere molte chiamate di funzione. Questo esempio fa un po 'di spazzatura arbitraria con un mucchio di dati, ma mostra che puoi fare cose divertenti. Le istruzioni print sono esempi di funzioni che restituiscono None(così come il .sort()metodo) ma aiutano anche a mostrare cosa lambdasta facendo.

>>> (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)]

7

Usa la funzione ordinata , in questo modo:

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

ah, grazie (che nome descrittivo per la funzione!) Ancora curioso dell'istruzione multipla all'interno della parte lambda. Possibile?
ottodidakt

1
No, "le funzioni create con i form lambda non possono contenere istruzioni". docs.python.org/reference/expressions.html#lambda
alex vasi

1
-1: lambda e mappa invece di comprensioni di lista? Non molto pitonico.
nikow

@nikow [x [1] for x in ordinato (elenco)] - modo pitonico?
RaGa__M

@Explorer_N: non la lista deve essere ordinata, ma gli elementi della lista (che sono liste) devono essere ordinati nel problema attuale. Quindi, piuttosto: [sorted(x)[1] for x in list_of_lists].
Max

6

Puoi infatti avere più istruzioni in un'espressione lambda in python. Non è del tutto banale ma nel tuo esempio, il seguente funziona:

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

Devi assicurarti che ogni istruzione non restituisca nulla o se la racchiuda (.. e False). Il risultato è ciò che viene restituito dall'ultima valutazione.

Esempio:

>>> f = (lambda : (print(1) and False) or (print(2) and False) or (print(3) and False))
>>> f()
1
2
3


4

Un modo Hacky per combinare più istruzioni in una singola istruzione in Python è usare la parola chiave "e" come operatore di cortocircuito. Quindi puoi usare questa singola istruzione direttamente come parte dell'espressione lambda.

Questo è simile all'uso di "&&" come operatore di cortocircuito in linguaggi shell come bash.

Nota anche: puoi sempre correggere un'istruzione di funzione per restituire un valore vero avvolgendo la funzione.

Esempio:

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

A pensarci bene, probabilmente è meglio usare "o" invece di "e" poiché molte funzioni restituiscono "0" o Nessuno in caso di successo. Quindi puoi sbarazzarti della funzione wrapper nell'esempio sopra:

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

junky("a", "b")

'and' operate valuterà le espressioni fino a quando non arriva al primo valore di ritorno zero. dopodiché va in cortocircuito. 1 e 1 e 0 e 1 valutano: 1 e 1 e 0 e diminuisce 1

L'operazione 'o' valuterà le espressioni fino a quando non arriva al primo valore restituito diverso da zero. dopodiché va in cortocircuito.

0 o 0 o 1 o 0 restituisce 0 o 0 o 1 e diminuisce 0


3

O se vuoi evitare lambda e avere un generatore invece di un elenco:

(ordinato (col) [1] per col in lst)


2

Lascia che ti presenti un trucco glorioso ma terrificante:

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)

Ora puoi utilizzare questo LETmodulo come tale:

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

che dà: [345, 465, 333]



1

Puoi farlo in O (n) tempo usando min e index invece di usare sort o heapq.

Prima crea un nuovo elenco di tutto tranne il valore minimo dell'elenco originale:

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

Quindi prendi il valore minimo del nuovo elenco:

second_smallest = min(new_list)

Ora tutti insieme in un unico lambda:

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

Sì, è davvero brutto, ma dovrebbe essere algoritmicamente economico. Inoltre, poiché alcune persone in questo thread vogliono vedere le comprensioni degli elenchi:

[min(x[:x.index(min(x))] + x[x.index(min(x))+1:]) for x in lst]

1

Questo è esattamente ciò che la bindfunzione in una monade scopo per cui viene utilizzata .

Con la bindfunzione puoi combinare più lambda in un lambda, ogni lambda rappresenta un'istruzione.


1

In realtà esiste un modo per utilizzare più istruzioni in lambda. Ecco la mia soluzione:

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

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

map(x, lst)

il codice all'interno execnon può essere
analizzato

0

Sì. Puoi definirlo in questo modo e quindi racchiudere le tue molteplici espressioni con quanto segue:

Inizio schema:

inizio = lambda * x: x [-1]

Progetto Lisp comune:

progn = lambda * x: x [-1]


0

Esistono soluzioni migliori senza utilizzare la funzione lambda. Ma se vogliamo davvero usare la funzione lambda, ecco una soluzione generica per gestire più istruzioni: map (lambda x: x [1] if (x.sort ()) else x [1], lst)

Non ti interessa davvero cosa restituisce l'affermazione.


0

Sì, è possibile. Prova sotto lo snippet di codice.

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

-1

Ti darò un'altra soluzione, fai in modo che il tuo lambda invochi una funzione.

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');

7
Ancora meglio: junky = multiple_statements.
Solomon Ucko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.