É possível ter várias instruções em uma expressão lambda python?

112

Eu sou um novato em python tentando alcançar o seguinte:

Eu tenho uma lista de listas:

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

Eu quero mapear em outra lista contendo apenas o segundo menor número de cada sublista. Portanto, o resultado deve ser:

[345, 465, 333]

Por exemplo, se eu estivesse interessado apenas no menor número, poderia fazer:

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

Eu gostaria de poder fazer isso:

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

mas a classificação não acorrenta. (retorna nenhum)

nem algo assim é permitido:

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

Existe uma maneira de fazer isso com map em python, mas sem definir uma função nomeada ? (é fácil com blocos anônimos em rubi, por exemplo)

ottodidakt
fonte
com certeza isso deve ser possível .. talvez em uma nova versão ...
ZEE
1
Você não pode executar instruções, mas pode chamar funções na função lambda, portanto, o truque sujo impotônico lambda x: sort(x) OR x[1]funcionaria: aqui o OR avalia seu primeiro argumento (valor de retorno Nenhum) como um bool (=> False) e, nesse caso, OR retorna seu segundo argumento. Mas, como dizem as respostas, é melhor evitar lambda.
Máx.

Respostas:

136

Há várias respostas diferentes que posso dar aqui, desde sua pergunta específica até questões mais gerais. Então, do mais específico ao mais geral:

P. Você pode colocar várias instruções em um lambda?

R. Não. Mas você realmente não precisa usar um lambda. Você pode colocar as instruções em um def. ie:

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

map(second_lowest, lst)

P. Você pode obter o segundo item mais baixo de um lambda classificando a lista?

A. Sim. Como aponta a resposta de alex , sorted()é uma versão do tipo que cria uma nova lista, em vez de classificação no local, e pode ser encadeada. Observe que provavelmente é isso que você deve usar - é uma má prática que seu mapa tenha efeitos colaterais na lista original.

P. Como devo obter o segundo item mais baixo de cada lista em uma sequência de listas?

A. sorted(l)[1] não é realmente a melhor maneira para isso. Ele tem complexidade O (N log (N)), enquanto uma solução O (n) existe. Isso pode ser encontrado no módulo heapq.

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

Então, basta usar:

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

Também é geralmente considerado mais claro usar uma compreensão de lista, o que evita o lambda por completo:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]
Brian
fonte
3
Não acho que você esteja certo sobre a solução O (n) encontrada no módulo heapq. Tudo o que você está fazendo é classificar a lista em heap, que é O (n log n) e, em seguida, encontrar os menores elementos.
avpx
8
A documentação ( docs.python.org/2/library/heapq.html#heapq.nsmallest ) realmente avisa que usar heapq.nsmallest () pode ser menos eficiente do que apenas usar Sort (), portanto, apenas as medições podem dizer qual solução é mais rápido no seu caso. heapq.nsmallest (), entretanto, tem uma complexidade de O (k * log (n) + n), eu acho, com n o comprimento da lista ek o número dos menores itens que você deseja extrair. O (n) para empilhar a lista ek vezes O (log (n)) para estourar k itens. Isso é melhor do que O (n * log (n)), especialmente para k pequeno.
Vortexfive
2
@Vortexfive e para constante k(como aqui com 'segundo mais baixo') O(k*log(n)+n)simplifica paraO(n)
Duncan
5
Tecnicamente, você também não precisa de um lambda para frases simples, mas certamente é mais conveniente. Esperançosamente, um suporte lambda melhor será adicionado mais tarde.
James,
1
@avpx: Não é um heapsort. Nós apenas mantemos um heap de 2 elementos dos 2 menores itens vistos, em vez de heapificar a lista inteira e estourar todos os elementos como um heapsort faria.
usuário2357112 suporta Monica
81

Colocar as declarações em uma lista pode simular várias declarações:

Por exemplo:

lambda x: [f1(x), f2(x), f3(x), x+1]
jmkg
fonte
9
Só para esclarecer, as chamadas de função não são consideradas instruções em Python, são expressões. Portanto, esta lista é apenas uma lista de expressões, que podem ser todas None.
Yawar
Acho que funciona porque ao analisar dinamicamente a coleção passada para o lambda o interpretador detecta assinaturas que podem ser chamadas e as executa ... você pode testar com -> lambda x: [print (x), print (x + 1), print (x +2)]
ZEE
5
Melhor ainda: lambda x: [f1 (x), f2 (x)] [- 1], ele retornará o resultado do cálculo da última expressão, como provavelmente esperado.
Anton Ovsyannikov
22

Viajante do tempo aqui. Se você geralmente deseja ter várias instruções dentro de um lambda, pode passar outros lambdas como argumentos para esse lambda.

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

Na verdade, você não pode ter várias instruções, mas pode simular isso passando lambdas para lambdas.

Edit: O viajante do tempo retorna! Você também pode abusar do comportamento das expressões booleanas (tendo em mente as regras de curto-circuito e a veracidade) para as operações em cadeia. Usar o operador ternário oferece ainda mais potência. Novamente, você não pode ter várias instruções , mas é claro que pode ter muitas chamadas de função. Este exemplo faz algum lixo arbitrário com um monte de dados, mas mostra que você pode fazer algumas coisas engraçadas. As instruções de impressão são exemplos de funções que retornam None(assim como o .sort()método), mas também ajudam a mostrar o que o lambdaestá fazendo.

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

Use a função classificada , como esta:

map(lambda x: sorted(x)[1],lst)
alex vasi
fonte
ah. Obrigado. (que nome descritivo para a função!) Ainda curioso sobre a declaração múltipla dentro da parte lambda. Possível?
ottodidakt
1
Não, "funções criadas com formulários lambda não podem conter instruções". docs.python.org/reference/expressions.html#lambda
alex
1
-1: lambda e map em vez de compreensões de lista? Não é muito python.
nikow
@nikow [x [1] para x na ordenada (lista)] - forma pítônica?
RaGa__M
@Explorer_N: não a lista deve ser classificada, mas os elementos da lista (que são listas) devem ser classificados no presente problema. Portanto, em vez: [sorted(x)[1] for x in list_of_lists].
Máx.
6

Na verdade, você pode ter várias instruções em uma expressão lambda em python. Não é totalmente trivial, mas em seu exemplo, o seguinte funciona:

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

Você tem que se certificar de que cada instrução não retorne nada ou se a envolva (.. e False). O resultado é o que é retornado pela última avaliação.

Exemplo:

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

Usando begin () a partir daqui: 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
fonte
4

Uma maneira Hacky de combinar várias instruções em uma única instrução em python é usar a palavra-chave "e" como um operador de curto-circuito. Em seguida, você pode usar essa única instrução diretamente como parte da expressão lambda.

Isso é semelhante a usar "&&" como o operador de curto-circuito em linguagens shell, como o bash.

Observe também: você sempre pode corrigir uma instrução de função para retornar um valor verdadeiro, envolvendo a função.

Exemplo:

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

Pensando bem, provavelmente é melhor usar 'ou' em vez de 'e', ​​pois muitas funções retornam '0' ou Nenhum em caso de sucesso. Então você pode se livrar da função de wrapper no exemplo acima:

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

junky("a", "b")

Operar 'e' avaliará as expressões até chegar ao primeiro valor de retorno zero. após o qual ele entra em curto-circuito. 1 e 1 e 0 e 1 avalia: 1 e 1 e 0, e cai 1

'or' operator irá avaliar as expressões até chegar ao primeiro valor de retorno diferente de zero. após o qual ele entra em curto-circuito.

0 ou 0 ou 1 ou 0 avalia 0 ou 0 ou 1 e descarta 0

Bill Moore
fonte
3

Ou se você quiser evitar lambda e ter um gerador em vez de uma lista:

(classificado (col) [1] para col em lst)

odwl
fonte
2

Deixe-me apresentar a você um hack glorioso, mas assustador:

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)

Agora você pode usar este LETformulário da seguinte forma:

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

que dá: [345, 465, 333]

divs1210
fonte
Postado originalmente aqui: gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210
1

Você pode fazer isso em tempo O (n) usando min e index em vez de sort ou heapq.

Primeiro crie uma nova lista de tudo, exceto o valor mínimo da lista original:

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

Em seguida, pegue o valor mínimo da nova lista:

second_smallest = min(new_list)

Agora todos juntos em um único lambda:

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

Sim, é realmente feio, mas deve ser algoritmicamente barato. Além disso, uma vez que algumas pessoas neste tópico desejam ver as compreensões de lista:

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

Isso é exatamente o que a bindfunção em uma Mônada é usada.

Com a bindfunção, você pode combinar vários lambda em um lambda, cada lambda representando uma instrução.

Jhegedus
fonte
1

Na verdade, existe uma maneira de usar várias instruções em lambda. Esta é minha solução:

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
fonte
o código interno execnão pode ser introspectado por um IDE inteligente
javadba
0

Sim. Você pode defini-lo dessa forma e, em seguida, agrupar suas várias expressões com o seguinte:

Início do esquema:

começar = lambda * x: x [-1]

Prognóstico Lisp comum:

progn = lambda * x: x [-1]

Anastasios
fonte
0

Existem soluções melhores sem usar a função lambda. Mas se realmente quisermos usar a função lambda, aqui está uma solução genérica para lidar com várias instruções: map (lambda x: x [1] if (x.sort ()) else x [1], lst)

Você realmente não se importa com o que a declaração retorna.

user11166890
fonte
0

Sim, é possível. Experimente o snippet de código abaixo.

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

Vou te dar outra solução, Faça seu lambda invocar uma função.

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
fonte
7
Ainda melhor: junky = multiple_statements.
Solomon Ucko