More discontinuous ranges in Python

I think I talked way too much in the latest episode of the Automators podcast, but David and Rosemary are indulgent hosts. One thing I’m glad I said—despite it being something I’ve said many times before—is that saving time isn’t necessarily the main reason build an automation. Consistency of results is at least as important as saving time. And then there’s the matter of keeping your skills sharp. I often create an automation just as a way to learn a new technique or to practice an old one that I haven’t used in a while. That’s what I did this past week.

I was writing up a plan to inspect a tall residential building. A certain architectural feature was of interest, and the inspections are to take place on those floors only. Here’s how the building is laid out:

The code I initially came up with to get a list of all the floors with the feature was this list comprehension:

python:
floors = [ i + 2 for i in range(52) if i % 8 < 5 ]

What I liked about this, apart from its compactness, was how all the pertinent figures appeared: 52 floors with pattern, starting on Floor 2, with the lower 5 floors in each group of 8 having the feature.

The result (after breaking it into lines where you can see the groups and gaps) was

[2, 3, 4, 5, 6,
 10, 11, 12, 13, 14,
 18, 19, 20, 21, 22,
 26, 27, 28, 29, 30,
 34, 35, 36, 37, 38,
 42, 43, 44, 45, 46,
 50, 51, 52, 53]

I was pretty pleased with myself until I remembered the superstition: there’s no “Floor 13.” Like many residential buildings, it skips from the 12th to the 14th, so if I wanted to give inspection directions that were easy to follow, I had to skip 13, too. I discussed this in a post last September.

One way to solve this—and the way I first solved it—is to loop through the floors list just created and bump up the floor numbers above 12:

python:
floors = [ i + 2 for i in range(52) if i % 8 < 5 ]
for i, f in enumerate(floors):
  floors[i] = f if f < 13 else f + 1

This worked, giving me

[2, 3, 4, 5, 6,
 10, 11, 12, 14, 15,
 19, 20, 21, 22, 23,
 27, 28, 29, 30, 31,
 35, 36, 37, 38, 39,
 43, 44, 45, 46, 47,
 51, 52, 53, 54]

but it wasn’t a particularly satisfying solution. If you’re using a comprehension to build the list in the first place, it feels like a defeat to then make adjustments using a plain old loop. So I thought some more and came up with this:

python:
floors = [ i + 2 if i < (13 - 2) else i + 3 \
           for i in range(52) if i % 8 < 5 ]

OK, I confess to being a little uneasy about making a comprehension that’s so long it needs to be broken into two lines to be understandable. But it is understandable, I think, and like the original, all the numbers in it have a meaning directly related to the layout of the building. Leaving the condition in the ternary operator as i < (13 - 2) instead of i < 11 helps a bit with the readability.1

So, did I save any time with this? No, I could have typed the whole list into an assignment statement in less time than I spent thinking about and writing either of the comprehensions. But being forced to think about the periodicity of the architectural features—which I hadn’t even considered when I was first looking through the drawings—gave me a better understanding of the building. And it’s always good to stretch out your language skills. Although I’ve used the ternary operator before, this was the first time I’d used it in a list comprehension.


  1. And if you’re wondering if leaving it that way makes the code run slower, I can tell you that running it though timeit shows that using 11 instead of (13 - 2) doesn’t speed up the code at all. Python’s compiler too smart for that.