A Rubyist Learns List Comprehensions

Google Cloud supports three dynamic languages, JavaScript, Python, and Ruby. One of my goals is to become competent enough to debug and make small patches to libraries in all three. I love Ruby, and I’ve written Javascript professionally, so coming up to speed on Python has been my latest “side” project. I worked through the Python Koans in February, and folks had warned me that list comprehensions are confusing. It turns out that while they are odd to my Ruby sensibilities they are not hard. Even better, some things that are hard in Ruby are one-liners in Python with list comprehensions. So if you are a Rubyist trying to learn Python, here are some of the basics of list comprehensions explained side by side with equivalent Ruby.

List Comprehensions?!?!

In Python list comprehensions are used in places where a Rubyists would chain methods from Enumerable. For example, if I wanted to return the squares of all multiples of three less than 50 in Ruby, I would write something like this.

1
(1...50).select { |x| x % 3 == 0 }.map { |x| x**2 }

This code uses a range and two methods from Enumerable. First I use select to extract the multiples of three. Then I use map to square the resulting array. Using a list comprehension in Python, I can do this in one step.

1
[x**2 for x in range(0, 50) if x % 3 == 0]

First, let me give a quick syntax explanation. The square brackets indicate that this is a list comprehension. The first part of a list comprehension is what you want at the end, in this case, I want squares, so my list comprehension starts with x**2. After that, I tell Python what numbers to square by specifying a range and how to iterate over it with for x in range(0, 50). Finally, I need to filter those results with a postfix if x % 3 ==0.

In my brain, the three pieces of a list comprehension map to “do this”, “for these values”, “in these situations”.

List Comprehension With Sections Color Coded

I like that the Python list comprehension matches pretty well with English syntax. The Ruby version, translated into English would be something like, “Take the numbers from 1 - 50, choose only the multiples of 3, and then square each of those.” It is understandable but feels pretty “mathy.”

But the best thing I learned about list comprehensions is that they are a fast way to generate all the pairwise combinations of two lists. I’m not sure how often you need to do that in production code it but comes up a lot in coding puzzles. Imagine you want all the combinations of one item from list A and one item from list B. If it helps pretend we’re making omelets.

1
2
A = ["Ham", "Chicken", "Veggies"]
B = ["Feta", "Cheddar"]

I don’t know a one line way to do this in Ruby. The code below is close, but the result doesn’t have the correct nesting.

1
A.map { |a| B.map { |b| [a, b] }}

With a list comprehension in Python, it is straightforward.

1
2
3
4
>>> [[x, y] for x in a for y in b]

[['Ham', 'Feta'], ['Ham', 'Cheddar'], ['Chicken', 'Feta'], 
['Chicken', 'Cheddar'], ['Veggies', 'Feta'], ['Veggies', 'Cheddar']]

For me, this is the killer feature of list comprehensions. I’m sure that there are community standards about how many layers deep is reasonable, but I like that I can take a concept that works on one list and seamlessly apply it to several lists or nested lists.

So Pythonistas and Rubyists, what did I get wrong? Let me know in the comments.