33
\$\begingroup\$

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.

\$\endgroup\$
4
  • \$\begingroup\$ 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. \$\endgroup\$
    – mbomb007
    Commented Jan 11, 2017 at 22:56
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Jan 12, 2017 at 7:54
  • 2
    \$\begingroup\$ For 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. \$\endgroup\$ Commented Jan 12, 2017 at 17:35
  • \$\begingroup\$ @JonathanAllan Good point, changed it to strip. \$\endgroup\$
    – xnor
    Commented Jan 12, 2017 at 21:42

6 Answers 6

9
\$\begingroup\$

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.

\$\endgroup\$
6
\$\begingroup\$

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))
\$\endgroup\$
3
  • \$\begingroup\$ Wow, that's smart! Why do I never see this kind of code? \$\endgroup\$
    – z0rberg's
    Commented Jan 12, 2017 at 23:11
  • 7
    \$\begingroup\$ @z0rberg's Probably because eval is evil. \$\endgroup\$
    – hyperneutrino
    Commented Jan 13, 2017 at 0:47
  • \$\begingroup\$ I don't subscribe to this codeism. #EvalLivesMatter ... but seriously, how is that worse than simple dll-injection? \$\endgroup\$
    – z0rberg's
    Commented Jan 13, 2017 at 11:37
2
\$\begingroup\$

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 )

\$\endgroup\$
0
\$\begingroup\$

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.
\$\endgroup\$
7
  • 3
    \$\begingroup\$ Function 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. \$\endgroup\$
    – FlipTack
    Commented Jan 12, 2017 at 16:25
  • \$\begingroup\$ @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. \$\endgroup\$
    – JAD
    Commented Jan 12, 2017 at 16:55
  • 1
    \$\begingroup\$ meta.codegolf.stackexchange.com/a/4940/60919 \$\endgroup\$
    – FlipTack
    Commented Jan 12, 2017 at 17:46
  • \$\begingroup\$ I think you can do better with a dictionary: lambda s,d={}:d.setdefault(0,s.lower())+d[0][::-1] It's also reusable. \$\endgroup\$
    – Zgarb
    Commented Jan 12, 2017 at 18:08
  • \$\begingroup\$ You can use list.extend to add multiple elements at once, which will be shorter than using list.append multiple times. \$\endgroup\$
    – mbomb007
    Commented Jan 12, 2017 at 22:55
0
\$\begingroup\$

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

add = lambda x, y: exec("x+=y")
\$\endgroup\$
1
  • \$\begingroup\$ alternatively:{add = lambda x, y: [x.append(x[0]+y), x.reverse(), x.pop()]} \$\endgroup\$ Commented May 29, 2018 at 5:10
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.