Is it possible to have multiple statements in a python lambda expression?

Question:

I have a list of lists:

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

I want map lst into another list containing only the second smallest number from each sublist. So the result should be [345, 465, 333].

If I were just interested in the smallest number, I could write this as map(lambda x: min(x), lst). To get the second smallest numbers, I thought of sorting and then indexing the results, like map(lambda x: sort(x)[1], lst); however, sort returns None rather than chaining.

If it were possible to use multiple statements in a lambda, I could write map(lambda x: sort(x); x[1], lst), but this is not allowed.

Can I use map to solve the problem without defining a named function? How?

Asked By: ottodidakt

||

Answers:

Use sorted function, like this:

map(lambda x: sorted(x)[1],lst)
Answered By: alex vasi

Or if you want to avoid lambda and have a generator instead of a list:

(sorted(col)[1] for col in lst)
Answered By: odwl

There are several different answers I can give here, from your specific question to more general concerns. So from most specific to most general:

Q. Can you put multiple statements in a lambda?

A. No. But you don’t actually need to use a lambda. You can put the statements in a def instead. i.e.:

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

map(second_lowest, lst)

Q. Can you get the second lowest item from a lambda by sorting the list?

A. Yes. As alex’s answer points out, sorted() is a version of sort that creates a new list, rather than sorting in-place, and can be chained. Note that this is probably what you should be using – it’s bad practice for your map to have side effects on the original list.

Q. How should I get the second lowest item from each list in a sequence of lists?

A. sorted(l)[1] is not actually the best way for this. It has O(N log(N)) complexity, while an O(n) solution exists. This can be found in the heapq module.

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

So just use:

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

It’s also usually considered clearer to use a list comprehension, which avoids the lambda altogether:

[heapq.nsmallest(x,2)[1] for x in list_of_lists]
Answered By: Brian

Using begin() from here: 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]
Answered By: Janus Troelsen

Time traveler here. If you generally want to have multiple statements within a lambda, you can pass other lambdas as arguments to that lambda.

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

You can’t actually have multiple statements, but you can simulate that by passing lambdas to lambdas.

Edit: The time traveler returns! You can also abuse the behavior of boolean expressions (keeping in mind short-circuiting rules and truthiness) to chain operations. Using the ternary operator gives you even more power. Again, you can’t have multiple statements, but you can of course have many function calls. This example does some arbitrary junk with a bunch of data, but, it shows that you can do some funny stuff. The print statements are examples of functions which return None (as does the .sort() method) but they also help show what the lambda is doing.

>>> (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)]
Answered By: 2rs2ts

Putting the expressions in a list may simulate multiple expressions:

E.g.:

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

This will not work with statements.

Answered By: jmkg

You can do it in O(n) time using min and index instead of using sort or heapq.

First create new list of everything except the min value of the original list:

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

Then take the min value of the new list:

second_smallest = min(new_list)

Now all together in a single lambda:

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

Yes it is really ugly, but it should be algorithmically cheap. Also since some folks in this thread want to see list comprehensions:

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

This is exactly what the bind function in a Monad is used for.

With the bind function you can combine multiple lambda’s into one lambda, each lambda representing a statement.

Answered By: jhegedus

You can in fact have multiple statements in a lambda expression in python. It is not entirely trivial but in your example, the following works:

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

You have to make sure that each statement does not return anything or if it does wrap it in (.. and False). The result is what is returned by the last evaluation.

Example:

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

A Hacky way to combine multiple statements into a single statement in python is to use the “and” keyword as a short-circuit operator. Then you can use this single statement directly as part of the lambda expression.

This is similar to using “&&” as the short-circuit operator in shell languages such as bash.

Also note: You can always fix a function statement to return a true value by wrapping the function.

Example:

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

On second thought, its probably better to use ‘or’ instead of ‘and’ since many functions return ‘0’ or None on success. Then you can get rid of the wrapper function in the above example:

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

junky("a", "b")

‘and’ operate will evaluate the expressions until it gets to the first zero return value. after which it short-circuits.
1 and 1 and 0 and 1
evaluates: 1 and 1 and 0, and drops 1

‘or’ operate will evaluate the expressions until it gets to the first non-zero return value. after which it short-circuits.

0 or 0 or 1 or 0
evaluates 0 or 0 or 1, and drops 0

Answered By: Bimo

I’ll give you another solution, Make your lambda invoke a function.

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');
Answered By: Bimo

Let me present to you a glorious but terrifying 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)

You can now use this LET form as such:

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

which gives: [345, 465, 333]

Answered By: divs1210

There actually is a way you can use multiple statements in lambda.
Here’s my solution:

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

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

map(x, lst)
Answered By: EternalCrafter_606

Yes. You can define it this way and then wrap your multiple expressions with the following:

Scheme begin:

begin = lambda *x: x[-1]

Common Lisp progn:

progn = lambda *x: x[-1]

Answered By: Anastasios

There are better solutions without using lambda function. But if we really want to use lambda function, here is a generic solution to deal with multiple statements:
map(lambda x: x[1] if (x.sort()) else x[1],lst)

You don’t really care what the statement returns.

Answered By: user11166890

Yes it is possible. Try below code snippet.

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()
Answered By: Ravi

to demonstrate the lambda x:[f1(),f2()] effect which enables us to execute multiple functions in lambda. it also demonstrates the single line if else conditions if you really want to shrink the code.

  • note that f1() can be a lambda function also(recursive lambda or
    lambda within lambda). and that inner lambda can be a statement/function of
    your choice.
  • you can also put exec(‘statement’) for example lambda x:[exec('a=[1]'),exec('b=2')]

a python implementation of touch(linux) command which creates empty files if they are not already existing.

def touch(fpath):
    check= os.path.exists(fpath)
    (lambda fname1:[open(fname1,"w+",errors="ignore").write(""),print('Touched',fname1)] 
    if not check else None) (fpath)

will print [ Touched fpath ] where fpath is file path given as input. will do nothing if file already exist.

the (lambda x: [ f(x), f2(x) ] ) (inp) <- we pass the ‘inp‘ as input to lambda which in this case is the fpath.

Answered By: nikhil swami

After analyzing all solutions offered above I came up with this combination, which seem most clear ad useful for me:

func = lambda *args, **kwargs: "return value" if [
    print("function 1..."),
    print("function n"),
    ["for loop" for x in range(10)]
] else None

Isn’t it beautiful?
Remember that there have to be something in list, so it has True value.
And another thing is that list can be replaced with set, to look more like C style code, but in this case you cannot place lists inside as they are not hashabe

Answered By: Christopher

I made class with methods using lamdas on one line:

(code := lambda *exps, ret = None: [exp for exp in list(exps) + [ret]][-1])(Car := type("Car", (object,), {"__init__": lambda self, brand, color, electro = False: code(setattr(self, "brand", brand), setattr(self, "color", color), setattr(self, "electro", electro), setattr(self, "running", False)), "start": lambda self: code(code(print("Car was already running, it exploded.nLMAO"), quit()) if self.running else None, setattr(self, "running", True), print("Vrooom")), "stop": lambda self: code(code(print("Car was off already, it exploded.nLMAO"), quit()) if not self.running else None, setattr(self, "running", False), print("!Vrooom")), "repaint": lambda self, new_color: code(setattr(self, "color", new_color), print(f"Splash Splash, your car is now {new_color}")), "drive": lambda self: code(print("Vrooom") if self.running else code(print("Car was not started, it exploded.nLMAO"), quit())), "is_on": lambda self: code(ret = self.running)}), car := Car("lamborghini", "#ff7400"), car.start(), car.drive(), car.is_on(), car.drive(), car.stop(), car.is_on(), car.stop())

more readable variation:

(
    code :=
    lambda *exps, ret = None:
    [
        exp
        for exp
        in list(exps) + [ret]
    ][-1]
)(

Car := type(
    "Car",
    (object,),
    {
        "__init__": lambda self, brand, color, electro = False: code(
            setattr(self, "brand", brand),
            setattr(self, "color", color),
            setattr(self, "electro", electro),
            setattr(self, "running", False)

        ),
        "start": lambda self: code(
            code(
                print("Car was already running, it exploded.nLMAO"),
                quit()
            ) if self.running
            else None,
            setattr(self, "running", True),
            print("Vrooom")
        ),
        "stop": lambda self: code(
            code(
                print("Car was off already, it exploded.nLMAO"),
                quit()
            ) if not self.running
            else None,
            setattr(self, "running", False),
            print("!Vrooom")
        ),
        "repaint": lambda self, new_color: code(
            setattr(self, "color", new_color),
            print(f"Splash Splash, your car is now {new_color}")        
        ),
        "drive": lambda self: code(
            print("Vrooom") if self.running
            else code(
                print("Car was not started, it exploded.nLMAO"),
                quit()
            )
        ),
        "is_on": lambda self: code(
            ret = self.running
        )
    }
),

car := Car("lamborghini", "#ff7400"),
car.start(),
car.drive(),
car.is_on(),
car.drive(),
car.stop(),
car.is_on(),
car.stop()
)

I use lambda function here that takes any number of arguments and return ret argument default to None to be able to have more expressions on one line splitted by ",".

Answered By: Batmates

While reading this long list of answers, I came up with a neat way of abusing tuples:

lambda x: (expr1(x), expr2(x), ..., result(x))[-1]

This will produce a lambda that evaluates all the expressions and returns the value of the last one. That only makes sense if those expressions have side-effects, like print() or sort().

Normal assignments (=) aren’t expressions, so if you want to assign a variable in a lambda you have to use := operator (added in Python 3.8) that also returns the assigned value:

lambda: (answer := 42, question := compute(answer), question)[-1]

But the above only works for local variable assignments. To assign e.g. a dict element you will have to use d.update({'key': 'value'}), but even with a list there is no similar option, l.__setitem__(index, value) has to be used. Same with deletion: Standard containers support x.pop(index) that is equivalent to del x[item] but also returns deleted value, but if it is unavailable you have to use x.__delitem__(index) directly.

To affect global variables, you have to modify the dict returned by globals():

lambda: (g := globals(), g.__setitem__('answer', 42), g.__delitem__('question'), None)[-1]

Lists, dicts or even sets can be used instead of tuples to group expressions into one, but tuples are faster (mostly because immutable) and don’t seem to have any disadvantages.

Also, DON’T USE ANY OF THIS IN PRODUCTION CODE! Pretty please!

Answered By: EvgEnZh

I can offer you these three ways:

map(lambda x: sorted(x)[1], lst))
map(lambda x: min(x[:x.index(min(x))]+x[x.index(min(x))+1:]), lst)
map(lambda x: min([num for num in x if num != min(x)]), lst)
Answered By: narcissus313

I know this is an old thing but it has more relevant answers. So, based on narcissus313’s answer I have a small correction to make:

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

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

I know this is a small thing and it’s rather obvious but it won’t work without the missing bracket. The thing is that lambda expressions can’t take multiple arguments but they can take a list/tuple of multiple actions.

Answered By: Aleks K.
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.