From people willing to learn all the ins and outs of a programming language to people wanting to put their esoteric language skills to use – code golf has always been the type of competition to attract a variety of programmers. While some shake their head in disbelief whilst looking at golfed code, others shed a tear of joy after managing to get rid of another letter in their already cramped code. No matter the views, code golf is a way to learn something new about a language that you’ve already been familiar with for a long time.
With the CodinGame platform supporting only the conventional languages, Python is among the best golfing languages available, being slightly more verbose than Perl, Ruby or Bash. As a result, Python, being as popular as it is, is the perfect language to try golfing an exercise or two and getting to experience competitive programming in a brand new way.
The following cheat sheet with the most used code golf tricks is aimed at both newbies looking to improve their golfing skills and veterans who want to make sure they know all the tricks in the book. Whether it’s a shortest Clash of Code, a CodinGame golf puzzle or a hole on Code-Golf.io, there’s always an unnecessary character or two hiding in your code. 😉
A good place to start looking for those pesky characters that should be removed: the if statements and their lengthy conditions:
if X and Y: print(B) else print(A)
First and most important thing here is realizing that if and else statements take up a lot of space but are rarely needed.
The example above can be put into one line like this:
print([A,B][X and Y])
When it comes to logic operators and code golf, a good rule of thumb to shorten your (Python) code is to try to get rid of as many and operators as possible. Let’s look at a couple of examples how that can be done.
For Boolean values X and Y, the and operator can be simply replaced with the bitwise &; that will do the job just fine:
print([A,B][X and Y]) #21 Chars print([A,B][X&Y]) #17 Chars
Comparisons can also sometimes be merged to get rid of the and operator(s):
print([A,B][X<5and Y<5]) #24 Chars print([A,B][X<5>Y]) #19 Chars print([A,B][X>0and X<10]) #25 Chars print([A,B][0<X<10]) #20 Chars print([A,B][X>0and Z>0and Y<X]) #31 Chars print([A,B][Z>0<X>Y]) #21 Chars #Note that you can leave the space out before #an operator if it is preceded by a number!
Conditionals checking for equality can sometimes be bundled up into tuples:
return X==5 and Y==6 #19 Chars return(X,Y)==(5,6) #18 Chars
But what if the condition is a simple Boolean? What do we optimize then…?
One might say there’s nothing left to improve in such a short line of code like:
Surprisingly, depending on the values of A and B, there’s still a lot of room for improvement. In cases where both A and B are integers, the line can be easily rewritten as follows:
print([5,9][X]) #15 Chars print(5+4*X) #12 Chars #4 is just the difference between integers A and B!
If the difference is equal to one, this can be further simplified into:
print([5,6][X]) #15 Chars print(5+X) #10 Chars
And if one of the numbers was equal to one, then it can be rewritten like:
print([1,6][X]) #15 Chars print(6**X) #11 Chars
Python has no problem doing arithmetic between a Boolean and a string, which means similar methods can be applied to strings as well:
print(['hello','world'][X]) #27 Chars print(X*'world'or'hello') #25 Chars
What if instead of a Boolean, we have an index X and more than two strings in a list? If all of the strings have equal length (or if strings that are shorter by one letter are put at the end), then there’s a beautiful way to cut down even more characters in your code:
print(['much','code','wow'][X]) #31 Chars print('mcwuoocdwhe'[X::3]) #26 Chars
So what just happened in that code and why is it working? The explanation is simple – 3 (the number of words that we have), is also the increment of our slicing, meaning that the output will be every 3rd letter, starting from the Xth letter. Such a string can be easily made by first writing the 1st letter of every word, followed up by the 2nd, etc., until the last one.
If what you’re outputting is a list (or any iterable for that matter) that also has to formatted with ‘ ‘.join() before output, remember that Python print() can do all the formatting for you:
print(', '.join(['A','B'])) # 27 Chars print(*['A','B'],sep=', ') # 26 Chars
Just like if statements, for-loops are a gold mine when it comes to code golf. If there’s one thing you should always try to avoid, it’s having nested for–loops. Normally you can write all of your code in just one line to avoid wasting characters on indentation; however, that will not work with nested if statements and for–loops, meaning you will be losing extra characters on spaces.
for a in range(3): for b in range(5): print(a,b) # One space is enough to indent for a in range(3): for b in range(5):print(a,b) # SyntaxError! for a in range(3):for b in range(5):print(a,b)
To avoid that, the loops can be merged into one, and the value of a and b can be extracted later with the help of modulo:
for a in range(X): for b in range(Y):print(a,b) #48 Chars for a in range(X*Y):print(a//X,a%X) #35 Chars
In specific cases where the value of the control variable is irrelevant, we can drop the range() function completely and replace it with something shorter:
for a in range(10):foo() #24 Chars for a in*10:foo() #20 Chars
…or get rid of the for loop as a whole:
exec("foo();"*10) #17 Chars exec"foo();"*10 #15 Chars (Py2)
Another trick that often gets overlooked is that range() of length 4 or smaller can be shorter when written as a tuple:
for a in range(4):print(a) #26 Chars for a in 0,1,2,3:print(a) #25 Chars
Range() of length 5 is also shorter as a tuple if the integers are shifted but are in single digits. Same goes for range() counting in reverse or with steps!
Variables and Conversion
Converting from one type to another or storing an excessive amount of data in variables can cost a lot of characters. A good example is converting from float to int after performing division:
x=math.floor(A/B) #17 Chars x=math.ceil(A/B) #16 Chars
Both operations can be made a lot shorter:
x=A//B #6 Chars (floor) x=-(-A//B) #10 Chars (ceil)
While everyone seems to know that // truncates the float, I’ve rarely seen people use the truncation to replace math.ceil().
Another common scenario is converting an iterable to a list like so:
A=list(range(10)) #17 Chars B=list('abc') #13 Chars
Starred assignment helps with iterable unpacking, making the code shorter:
*A,=range(10) #13 Chars *B,='abc' #9 Chars
Similar to how a string can be unpacked into one variable, it can be unpacked into multiple ones:
A,B,C='a','b','c' #17 Chars A,B,C='abc' #11 Chars
Just like storing commonly used values into a variable is a good practice, the same can be done with commonly used function names:
a,b,c=input(),input(),input() #29 Chars i=input;a,b,c=i(),i(),i() #25 Chars
Saving the best for last, we have lists, and a good replacement for almost every commonly used list method. Lengthy methods like append or extend take up way too much space to be used in code golf, and we can immediately start saving characters on the line where we created our list:
A,B=4, #8 Chars A,*B=4, #7 Chars
Using the same starred assignment, also known as “extended iterable unpacking”, makes it possible to retrieve elements from a list and/or modify the list by removing elements from it with fewer characters than normal:
#Getting first item A=L #6 Chars A,*_=L #6 Chars #Getting last item A=L[-1] #7 Chars *_,A=L #6 Chars #Removing first item L.pop(0) #8 Chars L=L[1:] #7 Chars _,*L=L #6 Chars #Removing last item L=L[:-1] #8 Chars L.pop() #7 Chars *L,_=L #6 Chars
It is easy to see that starred assignment makes list manipulating a lot shorter. Just like mentioned above, the most common list methods also have their own replacements for golf:
L.append(A) #11 Chars #Appending an item L+=[A] #6 Chars A.extend(B) #11 Chars #Extending a list A+=B #4 Chars L.insert(i,A) #13 Chars #Inserting items into a list L[:i]+=A #8 Chars L=L.reverse() #13 Chars #Reversing a list L=L[::-1] #9 Chars
Another common difficulty you might encounter when working with lists is retrieving elements for the back of the list. Simply adding a minus in front of the index will not yield the results you might want, since -0 is still 0. The usual solution to this is simply reversing the list before indexing; however, it can be done in much lot more elegant way:
L[::-1][A] #10 Chars L[~A] #5 Chars
It’s a good idea to use a list if you know you’ll have to calculate the number of elements you have. This can be done quickly with a short len(). However, you don’t always have the luxury of using lists, and might end up converting maps or other iterables to lists just because you want to know the number of elements that it contains! While the most intuitive way to convert something to a list is by using the in-built list(), turns out that’s a waste of characters:
A = combinations("ABC",2) # [('A', 'B'), ('A', 'C'), ('B', 'C')] print(len(A)) # TypeError! print(len(list(A))) # 19 Chars print(len([*A])) # 16 Chars
Did this blog post inspire you to try golfing? For the occasion, CodinGame agreed to release a new Code Golf puzzle, created from the well-known Chuck Norris puzzle. Feel free to try it and see how you stack up against others!
My current score to beat: 127 characters in Python 3.
Happy golfing 🙂
About the Author:
Unfortunately no hidden meaning besides that I’m a box made of ice 😂