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


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]

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]

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

7

Use a função classificada , como esta:

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

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


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


3

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

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


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]



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]

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.


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)

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]


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.


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

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

7
Ainda melhor: junky = multiple_statements.
Solomon Ucko
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.