Tweetable Python


Sometimes when I have nothing better to do, or when I do have something better to do but I really don’t want to do it, I play code golf with snippets of my work – trying to compress my bits of logic down to something tiny enough that I can tweet it. They aren’t efficient, they’re certainly not pretty, but there’s some satisfaction in squeezing out every last character I can while still maintaining the original functionality. I tend to write my golfs as standalone functions, so even though there’s no sane reason to use them as they’re currently formatted, they’re fully functional so I can play with them later. This isn’t a particularly educational post, I just enjoy these as puzzles and I love to share games like this with people who enjoy the same types of problems.

Dictionary Inversion – 75

Dictionary inversion, at least in python, is a nice simple operation (particularly with the dictionary comprehensions of 2.7+). Still, a basic implementation (disregarding handling of duplicate values) is a good warmup for a problem I had to tackle a couple of years ago.

def a(d):return{v:k for k,v in d.items()}
# for 2.6ers, def a(d):return dict(v,k for k,v in d.items()

The actual task I was given was to take a dictionary of values to lists of values and reverse it such that each entry in the lists pointed to a list of keys that pointed to lists which contained that element. I’ve included an example below to clarify the requirements.

# Given a dictionary of the form
d = {1: [1,2], 2: [2,3]}
# The function should return a dictionary of the form
{1: [1], 2: [1,2], 3: [2]}

# Solution:
def a(d):return{i:[k for k in d if i in d[k]]for v in d.values()for i in v}

# Or, in a slightly more readable form:
def a(d):
 return{i:
  [k for k in d if i in d[k]]
  for v in d.values()for i in v
 }

Pretty printer – 132

The goal here was to take a two-dimensional array of strings and print them neatly in a tab-delineated table. This implementation assumes an eight-space tab size, but is easily adaptable to other sizes.

My ‘pretty printer’ is one of the few old code golf examples where I still have some records of my previous iterations of compression. In the interest of sharing my process for code golfing, here are a few of the steps I still have copies of.

# So, given the following input
table = [
  ["Name", "Age", "City"],
  ["Bob", "23", "Detroit"],
  ["Angelina", "103", "San Francisco"]
]

# The function will print
# Name          Age     City
# Bob           23      Detroit
# Angelina      103     San Francisco

def pretty_printer(rows):
  columns = zip(*rows)
  max_widths = [max(len(value) for value in column) for column in columns]
  for row in rows:
    print '\t'.join(value + "\t" * (max_widths[col]/8 - len(value)/8) for col, value in enumerate(row))

The first iteration, as you can see, is entirely uncompressed. The variables are well named for clarity, and I even use a for-loop instead of a comprehension to keep things nice and simple. The only particularly interesting line is columns = zip(*rows), which splats the sequence of rows into zip(), which then combines all the first, second, third, etc., elements into new lists, giving us the columns instead.

def p(t):print'\n'.join('\t'.join(v+'\t'*([max(len(e) for e in c) for c in zip(*t)][i]/8-len(v)/8)for i, v in enumerate(r)) for r in t)

# Or, with line breaks
def p(t):
  print'\n'.join(
    '\t'.join(
      v+'\t'*([max(len(e) for e in c) for c in zip(*t)][i]/8-len(v)/8) 
    for i, v in enumerate(r)) 
  for r in t)

My first pass striped out all the nice naming, most of the whitespace, and compressed it down to one line by putting the variable value generation in where ‘columns’ and ‘max_widths’ used to be. Instead of printing line by line I also switched to using ‘\n’.join(), which combines a list of strings with line breaks between each.

def p(t):print'\n'.join('\t'.join(v+'\t'*([max(map(len,c))for c in zip(*t)][i]/8-len(v)/8)for i,v in enumerate(r))for r in t)


# Again, with some line breaks for readability
def p(t):
  print'\n'.join(
    '\t'.join(
      v+'\t'*([max(len(e)for e in c)for c in zip(*t)][i]/8-len(v)/8)
    for i, v in enumerate(r))
  for r in t)

I shaved off the final 10 characters by trimming out the whitespace I missed on the first pass, and replacing a comprehension with a map.

Not shown here are more than a few false starts that I remember trying but unfortunately didn’t write down. The difference between pretty good and excellent code golfing is generally just a few characters, so there’s a lot of experimentation that goes on where you do things like trying to see if a particular case works best as a list comprehension or a map operation, or whether it’s worth the overhead of creating an interim variable if you can avoid the character-cost of recomputation later.

Threaded Numbers – 83

I wrote this problem specifically for a code golf competition, so while it doesn’t have any practical use it makes for a good little puzzle in more than a few languages. I remember distinctly seeing a pretty impressive selection of approaches, even just within python (though there were plenty of interesting other language entries as well). If you’re looking to play around with code golfing, this would be one of the first I recommend you try out.

The object of the exercise is to open a file in the same directory as your program, which will contain a sequence of randomly ordered integers that are delineated by spaces. Your program should read the integers, and then output them in a very specific sorted format. The outputted list should also be space-delineated, but should be ordered such that the smallest number is the leftmost in the sequence, the second smallest is the rightmost, the third smallest the second leftmost, and so on.

# If the file 'in.txt' contains "1 3 5 8 2 9 4 6 8 10" 
# the program should output 1 3 5 8 9 10 8 6 4 2

x=sorted(open('in.txt').read().split(),key=int)
print' '.join(x[::2]+x[1::2][::-1])

And just for fun, here’s one of my other (less successful) attempts at the same problem. It relies on the trick of passing the same iterator twice to zip, so that items are pulled off the sorted list in pairs and dropped into two separate new sequences.

x=iter(sorted(open('in.txt').read().split(),key=int))
a=zip(*zip(x,x))
print' '.join(a[0]+a[1][::-1])

Leave a Reply

Your email address will not be published. Required fields are marked *