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

Python

Python Problem Overview


I am a python newbie trying to achieve the following:

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]

For example if I were just interested in the smallest number, I could do:

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

I wish I could do this:

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

but sort does not chain. (returns None)

neither is something like this allowed:

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

Is there a way to do this with map in python but without defining a named function? (it is easy with anonymous blocks in ruby, for example)

Python Solutions


Solution 1 - Python

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]

Solution 2 - Python

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.

Solution 3 - Python

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

Solution 4 - Python

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

Solution 5 - Python

Use sorted function, like this:

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

Solution 6 - Python

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

Solution 7 - Python

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]

Solution 8 - Python

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!

Solution 9 - Python

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

(sorted(col)[1] for col in lst)

Solution 10 - Python

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

Solution 11 - Python

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]

Solution 12 - Python

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]

Solution 13 - Python

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.

Solution 14 - Python

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

Solution 15 - Python

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)

Solution 16 - Python

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]

Solution 17 - Python

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.

Solution 18 - Python

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

Solution 19 - Python

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.

Solution 20 - Python

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 ",".

Solution 21 - Python

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)

Solution 22 - Python

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionottodidaktView Question on Stackoverflow
Solution 1 - PythonBrianView Answer on Stackoverflow
Solution 2 - PythonjmkgView Answer on Stackoverflow
Solution 3 - Python2rs2tsView Answer on Stackoverflow
Solution 4 - PythonLoreno HeerView Answer on Stackoverflow
Solution 5 - Pythonalex vasiView Answer on Stackoverflow
Solution 6 - PythonBimoView Answer on Stackoverflow
Solution 7 - PythonJanus TroelsenView Answer on Stackoverflow
Solution 8 - PythonEvgEnZhView Answer on Stackoverflow
Solution 9 - PythonodwlView Answer on Stackoverflow
Solution 10 - PythonChristopher View Answer on Stackoverflow
Solution 11 - Pythondivs1210View Answer on Stackoverflow
Solution 12 - PythonnakedfanaticView Answer on Stackoverflow
Solution 13 - PythonjhegedusView Answer on Stackoverflow
Solution 14 - PythonBimoView Answer on Stackoverflow
Solution 15 - PythonEternalCrafter_606View Answer on Stackoverflow
Solution 16 - PythonAnastasiosView Answer on Stackoverflow
Solution 17 - Pythonuser11166890View Answer on Stackoverflow
Solution 18 - PythonRaviView Answer on Stackoverflow
Solution 19 - Pythonnikhil swamiView Answer on Stackoverflow
Solution 20 - PythonBatmatesView Answer on Stackoverflow
Solution 21 - Pythonnarcissus313View Answer on Stackoverflow
Solution 22 - PythonAleks K.View Answer on Stackoverflow