Czy można mieć wiele instrukcji w wyrażeniu lambda w języku Python?

112

Jestem nowicjuszem w Pythonie próbującym osiągnąć następujące cele:

Mam listę list:

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

Chcę mapować lst na inną listę zawierającą tylko drugą najmniejszą liczbę z każdej podlisty. Więc wynik powinien być:

[345, 465, 333]

Na przykład, gdybym był zainteresowany najmniejszą liczbą, mógłbym zrobić:

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

Chciałbym móc to zrobić:

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

ale sortowanie nie tworzy łańcucha. (zwraca Brak)

też coś takiego nie jest dozwolone:

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

Czy istnieje sposób na zrobienie tego z mapą w Pythonie, ale bez definiowania nazwanej funkcji ? (jest to łatwe, na przykład z anonimowymi blokami w rubinie)

ottodidakt
źródło
na pewno powinno to być możliwe ... może w nowej wersji ...
ZEE
1
Nie możesz wykonywać instrukcji, ale możesz wywoływać funkcje w funkcji lambda, więc niepytoniczny brudny hack lambda x: sort(x) OR x[1]zadziała: tutaj OR oblicza swój pierwszy argument (zwracana wartość None) jako bool (=> False), a w tym przypadku OR zwraca swój drugi argument. Ale jak mówią odpowiedzi, lepiej unikać lambda.
Max

Odpowiedzi:

136

Mogę tu udzielić kilku różnych odpowiedzi, od konkretnego pytania po bardziej ogólne wątpliwości. A więc od najbardziej szczegółowych do najbardziej ogólnych:

P. Czy możesz umieścić wiele instrukcji w lambdzie?

O. Nie. Ale tak naprawdę nie musisz używać lambdy. Zamiast tego możesz umieścić instrukcje w def. to znaczy:

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

map(second_lowest, lst)

P. Czy możesz uzyskać drugą najniższą pozycję z lambda, sortując listę?

A. Tak. Jak wskazuje odpowiedź Alexa, sorted()jest to wersja, która tworzy nową listę zamiast sortowania na miejscu i może być połączona łańcuchowo. Zauważ, że prawdopodobnie tego powinieneś używać - to zła praktyka, jeśli twoja mapa ma efekty uboczne na oryginalnej liście.

P. Jak mogę uzyskać drugą najniższą pozycję z każdej listy w sekwencji list?

A. sorted(l)[1] nie jest do tego najlepszym sposobem. Ma złożoność O (N log (N)), podczas gdy istnieje rozwiązanie O (n). Można to znaleźć w module heapq.

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

Więc po prostu użyj:

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

Zwykle uważa się również, że jaśniejsze jest użycie rozumienia list, które całkowicie pozwala uniknąć lambdy:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]
Brian
źródło
3
Myślę, że nie masz racji co do rozwiązania O (n) znajdującego się w module heapq. Wszystko, co tam robisz, to sortowanie listy na stosie, czyli O (n log n), a następnie znajdowanie najmniejszych elementów.
avpx
8
Dokumentacja ( docs.python.org/2/library/heapq.html#heapq.nsmallest ) rzeczywiście ostrzega, że ​​użycie heapq.nsmallest () może być mniej wydajne niż zwykłe użycie sort (), więc tylko pomiary mogą powiedzieć, które rozwiązanie jest najszybszy w Twoim przypadku. heapq.nsmallest () ma jednak złożoność O (k * log (n) + n), jak sądzę, gdzie n jest długością listy i k liczbą najmniejszych elementów, które chcesz wyodrębnić. O (n), aby ułożyć listę i k razy O (log (n)), aby zdjąć k elementów. Jest to lepsze niż O (n * log (n)), szczególnie dla małego k.
Vortexfive
2
@Vortexfive i dla stałej k(jak tutaj z „drugą najniższą”) O(k*log(n)+n)upraszcza doO(n)
Duncan
5
Z technicznego punktu widzenia nie potrzebujesz również lambdy dla jednowymiarowych, ale z pewnością jest to wygodniejsze. Miejmy nadzieję, że później zostanie dodana lepsza obsługa lambda.
James
1
@avpx: To nie jest kopalnia. Utrzymujemy tylko 2-elementową stertę dwóch najmniejszych widocznych elementów, zamiast układać całą listę i usuwać wszystkie elementy tak, jak zrobiłby to heapsort.
user2357112 obsługuje Monikę
81

Umieszczenie wyciągów na liście może symulować wiele instrukcji:

Na przykład:

lambda x: [f1(x), f2(x), f3(x), x+1]
jmkg
źródło
9
Dla wyjaśnienia, wywołania funkcji w Pythonie nie są uważane za instrukcje, są one wyrażeniami. Więc ta lista to tylko lista wyrażeń, które mogą być None.
Yawar
Myślę, że to działa, ponieważ podczas dynamicznej analizy zbioru przekazanego do lambdy interpreter wykrywa wywoływalne sygnatury i wykonuje je ... możesz testować z -> lambda x: [print (x), print (x + 1), print (x +2)]
ZEE
5
Jeszcze lepiej: lambda x: [f1 (x), f2 (x)] [- 1], zwróci wynik obliczenia z ostatniego wyrażenia, jak prawdopodobnie oczekiwano.
Anton Ovsyannikov
22

Podróżnik w czasie tutaj. Jeśli generalnie chcesz mieć wiele instrukcji w lambdzie, możesz przekazać inne lambdy jako argumenty do tej lambdy.

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

W rzeczywistości nie możesz mieć wielu instrukcji, ale możesz to zasymulować, przekazując lambdy do lambd.

Edycja: podróżnik w czasie powraca! Możesz także nadużywać zachowania wyrażeń boolowskich (pamiętając o krótkich regułach i prawdziwości) w operacjach łańcuchowych. Korzystanie z operatora trójskładnikowego zapewnia jeszcze większą moc. Ponownie, nie możesz mieć wielu instrukcji , ale możesz oczywiście mieć wiele wywołań funkcji. Ten przykład zawiera jakieś przypadkowe śmieci z dużą ilością danych, ale pokazuje, że możesz zrobić kilka zabawnych rzeczy. Instrukcje print są przykładami funkcji, które zwracają None(podobnie jak .sort()metoda), ale pomagają też pokazać, co lambdarobi.

>>> (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
źródło
7

Użyj posortowanej funkcji , takiej jak ta:

map(lambda x: sorted(x)[1],lst)
alex vasi
źródło
ah.Dzięki. (co za opisowa nazwa funkcji!) Wciąż ciekawi mnie wielokrotna instrukcja w części lambda. Możliwy?
ottodidakt
1
Nie, „funkcje utworzone za pomocą formularzy lambda nie mogą zawierać instrukcji”. docs.python.org/reference/expressions.html#lambda
alex vasi
1
-1: lambda i mapa zamiast list składanych? Niezbyt pytoniczne.
ników
@nikow [x [1] for x in sort (list)] - pythonic way?
RaGa__M
@Explorer_N: nie lista musi być sortowana, ale elementy listy (które są listami) muszą być sortowane w obecnym problemie. Więc raczej: [sorted(x)[1] for x in list_of_lists].
Max
6

W rzeczywistości możesz mieć wiele instrukcji w wyrażeniu lambda w Pythonie. Nie jest to całkowicie trywialne, ale w twoim przykładzie działa to:

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

Musisz się upewnić, że każda instrukcja nic nie zwraca lub zawija ją w (.. i False). Wynik jest tym, co jest zwracane przez ostatnią ocenę.

Przykład:

>>> f = (lambda : (print(1) and False) or (print(2) and False) or (print(3) and False))
>>> f()
1
2
3
Loreno Heer
źródło
5

Korzystanie z funkcji begin () tutaj: 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
źródło
4

Hackim sposobem łączenia wielu instrukcji w jedną instrukcję w Pythonie jest użycie słowa kluczowego „and” jako operatora zwarcia. Następnie możesz użyć tej pojedynczej instrukcji bezpośrednio jako części wyrażenia lambda.

Jest to podobne do używania „&&” jako operatora zwarcia w językach powłoki, takich jak bash.

Uwaga: zawsze można naprawić instrukcję funkcji, aby zwracała wartość true, opakowując funkcję.

Przykład:

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

Po drugie, prawdopodobnie lepiej jest użyć znaku „lub” zamiast „i”, ponieważ wiele funkcji po pomyślnym zakończeniu zwraca „0” lub „Brak”. Następnie możesz pozbyć się funkcji opakowującej z powyższego przykładu:

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

junky("a", "b")

Operacje „and” będą oceniać wyrażenia, aż dojdą do pierwszej zwracanej wartości zerowej. po czym zwiera. 1 i 1 oraz 0 i 1 oceniają: 1 i 1 i 0, a spadają 1

Operacja 'or' będzie oceniać wyrażenia, aż dojdzie do pierwszej niezerowej wartości zwracanej. po czym zwiera.

0 lub 0 lub 1 lub 0 daje 0 lub 0 lub 1 i spada 0

Bill Moore
źródło
3

Lub jeśli chcesz uniknąć lambdy i mieć generator zamiast listy:

(posortowane (kol) [1] dla kolumny w lst)

odwl
źródło
2

Pozwólcie, że przedstawię wam wspaniały, ale przerażający hack:

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)

Możesz teraz użyć tego LETformularza jako takiego:

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

co daje: [345, 465, 333]

divs1210
źródło
Pierwotnie opublikowane tutaj: gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
1

Możesz to zrobić w czasie O (n), używając min i index zamiast sortowania lub heapq.

Najpierw utwórz nową listę wszystkiego poza minimalną wartością oryginalnej listy:

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

Następnie weź minimalną wartość nowej listy:

second_smallest = min(new_list)

Teraz wszystko razem w jednej lambdzie:

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

Tak, jest naprawdę brzydki, ale algorytmicznie powinien być tani. Również ponieważ niektórzy ludzie w tym wątku chcą zobaczyć listy składane:

[min(x[:x.index(min(x))] + x[x.index(min(x))+1:]) for x in lst]
nakedfanatic
źródło
1

Dokładnie do tego bindsłuży funkcja w monadzie .

Za pomocą tej bindfunkcji można połączyć wiele lambda w jedną lambdę, z których każda reprezentuje instrukcję.

jhegedus
źródło
1

W rzeczywistości istnieje sposób, w jaki można używać wielu instrukcji w lambdzie. Oto moje rozwiązanie:

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
źródło
kod znajdujący się w środku execnie może być poddany introspekcji przez inteligentne IDE
javadba
0

Tak. Możesz to zdefiniować w ten sposób, a następnie opakować swoje wyrażenia w następujący sposób:

Schemat zaczyna się:

begin = lambda * x: x [-1]

Wspólna prognoza Lisp:

progn = lambda * x: x [-1]

Anastasios
źródło
0

Są lepsze rozwiązania bez użycia funkcji lambda. Ale jeśli naprawdę chcemy użyć funkcji lambda, oto ogólne rozwiązanie do radzenia sobie z wieloma instrukcjami: map (lambda x: x [1] if (x.sort ()) else x [1], lst)

Nie obchodzi cię, co zwraca oświadczenie.

user11166890
źródło
0

Tak to mozliwe. Wypróbuj poniższy fragment kodu.

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
źródło
-1

Dam ci inne rozwiązanie, spraw, aby twoja lambda wywoływała funkcję.

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
źródło
7
Nawet lepiej: junky = multiple_statements.
Solomon Ucko