Python workarounds for assignment in lambda

34

2

This is a tips question for golfing in Python.

In Python golfing, it's common for a submission to be a function defined as a lambda. For example,

f=lambda x:0**x or x*f(x-1)

computes the factorial of x.

The lambda format has two big advantages:

  • The boilerplate of f=lambda x:... or lambda x:... is shorter than the def f(x):...return... or x=input()...print...
  • A recursive call can be used to loop with little byte overhead.

However, lambdas have the big drawback of allowing only a single expression, no statements. In particular, this means no assignments like c=chr(x+65). This is problematic when one has a long expression whose value needs to be referenced twice (or more).

Assignments like E=enumerate are possible outside the function or as an optional argument, but only if they don't depend on the function inputs. Optional arguments like f=lambda n,k=min(n,0):... fail because input n hasn't been defined when k is evaluated at definition time.

The result is that sometimes you suck up repeating a long expression in a lambda because the alternative is a lengthy non-lambda.

lambda s:s.strip()+s.strip()[::-1]
def f(s):t=s.strip();print t+t[::-1]

The break even point is about 11 characters (details), past which you switch to a def or program. Compare this to the usual break-even point of length 5 for a repeated expression:

range(a)+range(b)
r=range;r(a)+r(b)

print s[1:],s[1:]*2
r=s[1:];print r,r*2

Other languages have workarounds, Octave for example. There are known tricks for Python, but they are long, clunky, and/or limited-use. A short, general-purpose method to simulate assignment in a lambda would revolutionize Python golfing.


What are ways for a Python golfer to overcome or work around this limitation? What potential ideas should they have in mind when they see a long expression repeated twice in a lambda?

My goal with this tips question is to dive deep into this problem and:

  • Catalog and analyze golfing workarounds to fake assignment inside a lambda
  • Explore new leads for better methods

Each answer should explain a workaround or potential lead.

xnor

Posted 2017-01-11T22:00:30.393

Reputation: 115 687

I'm guessing this is one of those things that can't be done well in Python. JavaScript has a leg up on this one. – mbomb007 – 2017-01-11T22:56:06.013

Just as with orlp's answer, Neil's (deleted) suggestion for using nested lambdas isn't necessarily longer than def in cases where you need a nested lambda anyway. I think it deserves more thorough analysis. – Martin Ender – 2017-01-12T07:54:36.400

2For the exact example given with the reversed lowercase string concatenation one could just go for lambda s:(s+s[::-1]).lower(). Of course this does not answer the actual question. – Jonathan Allan – 2017-01-12T17:35:01.777

@JonathanAllan Good point, changed it to strip. – xnor – 2017-01-12T21:42:08.050

Answers

6

eval

This is not that great in of itself, but if your solution already uses eval in some way or form you can usually use this technique.

eval("%f*%f+%f"%((5**.5,)*3))

orlp

Posted 2017-01-11T22:00:30.393

Reputation: 37 067

Wow, that's smart! Why do I never see this kind of code? – z0rberg's – 2017-01-12T23:11:49.763

6@z0rberg's Probably because eval is evil. – HyperNeutrino – 2017-01-13T00:47:39.013

I don't subscribe to this codeism. #EvalLivesMatter ... but seriously, how is that worse than simple dll-injection? – z0rberg's – 2017-01-13T11:37:58.323

6

Assignment expressions in Python 3.8

Python 3.8 (TIO) introduces assignment expressions, which use := to assign a variable inline as part of expression.

>>> (n:=2, n+1)
(2, 3)

This can be used inside a lambda, where assignments are not ordinarily allowed. Compare:

lambda s:(t:=s.strip())+t[::-1]
lambda s:s.strip()+s.strip()[::-1]
def f(s):t=s.strip();return t+t[::-1]

See this tip for more.

xnor

Posted 2017-01-11T22:00:30.393

Reputation: 115 687

2

Inner lambdas

These allow you to define multiple variables at once.

lambda s:s.strip()+s.strip()[::-1]

vs.

lambda s:(lambda t:t+t[::-1])(s.strip())

is much longer, but if you have multiple variables, or variables that are longer, which are repeated many times:

lambda a,b,c:a.upper()*int(c)+b.lower()*int(c)+a.upper()[::-1]+b.lower()[::-1]+a.upper()*int(c)+a.lower()*int(c)

vs.

lambda a,B,c:(lambda A,b,n:A*n+b*n+A[::-1]+b[::-1]+A*n+b*c)(a.upper(),B.lower(),int(c))

Character count

Initial: (lambda:)() (11 bytes)
First variable: [space]a (2 bytes)
Subsequent variables: ,b, (3 bytes)
Use: a (1 byte).

(lambda* a*_,b_:)(*<value a>*_,<value b>_)

(Also saves on brackets)

So, this takes 3n + 10 bytes, where n is the number of variables. This is a high initial cost, but can pay off in the end. It even returns it's inner value,so you can nest multiple (Though this will quickly become not worth it.)

This is really only useful for long intermediate calculations in nested list comprehensions, as a def f():a=...;b=...;return will usually be shorter.

For 1 value, this saves: uses * length - length - uses - 13, so is only useful when that expression is positive.
For a n different expressions used u times in total, where their combined length is l, this saves:
l - (3 * n) - u - 10 ( + brackets removed )

Artyer

Posted 2017-01-11T22:00:30.393

Reputation: 1 697

1

Use a List

Declare a list as parameter and use .append() or to store the value:
lambda s:s.lower()+s.lower()[::-1]
turns into
lambda s,l=[]:l.append(s.lower())or l[-1]+l[-1][::-1]

Character count:

,l=[] 5 characters
l.append()or 13 characters
l[-1] 5 characters for each use

Break Even

The amount of added character is :
uses*(5-length) + 18 + length
In the previous example the statement is s.lower() is 9 characters long and is used 2 times, applying this technique added 19 characters. If it was used 7 times, there would be a 1 character reduction.
The amount of minimal uses to this technique be worth is
min_uses = (18+length)/(length-5)

Upsides

  • Allow a new assignment at a slightly reduced cost (the list already is declared)
  • Is a list object so [0], .pop(), [x:y] and other list functions can be used for tricks. highly situational

Downsides

  • High initial cost
  • High use cost
  • Only works for uses with the length higher than 5

Use a Dictionary

thanks @Zgarb
Same idea as above Declare a dictionary as parameter and use .setdefault() to store (and return) the value:
lambda s:s.lower()+s.lower()[::-1]
turns into
lambda s,d={}:d.setdefault(0,s.lower())+d[0][::-1]
Note that, unlike the list counterpart, setdefault returns the value assigned.

Character count:

,d={} 5 characters
d.setdefault(k,) 16 characters
d[k] 4 characters for each use

Break Even

The amount of added character is :
(uses-1)*(4-length) + 21
In the previous example the statement is s.lower() is 9 characters long and is used 2 times, applying this technique added 16 characters. If it was used 7 times, there would be a 1 character reduction.
The amount of minimal uses to this technique be worth is
min_uses = 1-21/(4-length)

Upsides/Downsides

  • Basically the same as the list
  • Only works for uses with the length higher than 4

Other considerations

  • If this technique worth the character reduction, then the lambda can probably be dropped and the function be rewritten with def/input for a shorter program.
  • As @FlipTack pointed, the list and the dict ARE KEEPT between function calls, while this will mostly disturb, it can be used on recursive calls.again highly situational
  • The dictionary will always be shorter than the list.

Rod

Posted 2017-01-11T22:00:30.393

Reputation: 17 588

3Function submissions have to be usable more than once. Currently this uses the same list each time the lambda is run, which could mess up things on later runs. – FlipTack – 2017-01-12T16:25:54.620

@FlipTack out of interest, would you mind linking a source like meta? I think that rule might have some impact on a few tricks I know. – JAD – 2017-01-12T16:55:05.813

1http://meta.codegolf.stackexchange.com/a/4940/60919 – FlipTack – 2017-01-12T17:46:44.710

I think you can do better with a dictionary: lambda s,d={}:d.setdefault(0,s.lower())+d[0][::-1] It's also reusable. – Zgarb – 2017-01-12T18:08:13.437

You can use list.extend to add multiple elements at once, which will be shorter than using list.append multiple times. – mbomb007 – 2017-01-12T22:55:33.377

I think I'm missing something. Any reason to use l[-1] rather than l[0] for the list case? Is this just to allow the function to be called multiple times and work uniformly? – Julian Wolf – 2017-05-16T23:41:22.103

@JulianWolf basically, yes – Rod – 2017-05-17T01:21:44.017

0

Use to set variables and return the data after operation like such:

add = lambda x, y: exec("x+=y")

Dylan Eliot

Posted 2017-01-11T22:00:30.393

Reputation: 1

alternatively:{add = lambda x, y: [x.append(x[0]+y), x.reverse(), x.pop()]} – Dylan Eliot – 2018-05-29T05:10:45.900

0

List comprehensions

This is more of a last resort since it's so ungolfy, but you can do
[<expression> for <variable> in <value>]
to pseudo-set a variable in a lambda. Basically the only good point about this method is that the inner expression can stay readable, which is obviously the least of your concern when golfing.

ASCII-only

Posted 2017-01-11T22:00:30.393

Reputation: 4 687