Monday, October 22, 2018

Solution for A Python email signature puzzle

By Vasudev Ram



Hi, readers,

Here is the answer to the puzzle in my recent post titled A Python email signature puzzle:

The answer is that the program prints this output:
Truly rural
Truly rural
Truly rural
Truly rural
Truly rural






There are a few blank lines in the output, so I intentionally included them above.

I'll first mention that there was a mistake in the code in the post, and the fix for it, and will then describe how the code works, below:

In the 5th line of the code, i.e. in the line below the one in which the print keyword appears, the fragment:
1 >> 8
was a typo; it should really be:
1 << 8
i.e. 1 left-shifted by 8, not 1 right-shifted by 8.
Sorry about that.

I''ve reproduced the full (corrected) code below for your convenience:
for ix, it in enumerate([('\n', 2 >> 1), \
    ("larur ylurt", (8 >> 1) + 1), ('\n', 1 << 1),]):
    for tm in range(it[1]):
        print chr(ord(it[0][::-1][0]) - 2 ** 5) + \
            it[0][::-1][(1 << 8) - (2 ** 8 - 1):] \
            if ix % 2 == 1 else it[0] * int(bin(4) \
            and bin(1), 2) * ix
Now let's dissect the code a piece at a time to understand how it works:

The outer for loop starts with:
for ix, it in enumerate([('\n', 2 >> 1), \
    ("larur ylurt", (8 >> 1) + 1), ('\n', 1 << 1),]):
When evaluating or figuring out what nested expressions mean, we have to evaluate them from the inside out, because the inner expressions' values need to be first computed in order to be used as terms or arguments in outer ones.

So we can start with the argument to enumerate().

It is a list of 3 tuples.

Each tuple has two items.

The first tuple is:

('\n', 2 >> 1)
which evaluates to:
('\n', 1)
because
2 >> 1
equals
1

(Right-shifting a number by 1 is the same as dividing it by 2, BTW, and left-shifting a number by 1 is the same as multiplying it by 2. This is for integers. See Arithmetic shift.)

The second tuple is:

("larur ylurt", (8 >> 1) + 1)
which evaluates to:
("larur ylurt", 5)
because
(8 >> 1) + 1
equals
4 + 1
The third tuple is:
('\n', 1 << 1)
which evaluates to:
('\n', 2)
because
1 << 1
equals
2
So the list of tuples evaluates to:
[('\n', 1), ("larur ylurt", 5), ('\n', 2)]
(BTW, in Python, it is legal (although optional), to put a comma after the last item in a list, before the closing square bracket, as I did in the code. It is a convenience, so that when adding another item to the list later, maybe on the next line, if won't matter if you forget to add a comma before that new item. A bit of defensive programming there. Putting two consecutive commas is illegal, though.)

Now that we have evaluated the list of tuples, let's see what the outer for loop does with it. This bit:

for ix, it in enumerate(...)
where the ... is a placeholder for the argument to enumerate() (i.e. list of tuples), causes Python to run the block controlled by the outer loop, 3 times (since the list has 3 items), with ix and it set to the successive indices and item of the list, with i going from 0 to 2.

Within the outer for loop, the first (and only) statement is the inner for loop, which goes like this:

for tm in range(it[1]):
That creates a loop with tm going from 0 to it[1] - 1 (for each time the outer loop executes), where it[1] is the 2nd item of each tuple, i.e. the number that follows the string. But it[1] varies with each iteration of the outer loop.

So the inner for statement effectively runs 3 times (which is not the same as a for loop having 3 iterations). This is because there are 3 tuples in the list looped over by the outer for statement. The index for the inner for loop start at 0 each time, and goes up to the value of it[1] - 1 each time. This is because range(n) gives us 0 to n - 1.
Since it[1] is successively 1, then 5, then 2, the first time, the inner for loop has 1 iteration, then it has 5 iterations, and finally it has 2 iterations.

For the last part that needs to be explained, i.e. the print statement that is controlled by the inner for statement, I'll reverse direction temporarily, i.e. I'll first go outside in, for the main components of the print statement, then for each detailed part, I'll again go inside out.

The print statement prints the evaluated value of a Python conditional expression. See later in this post for background information about conditional expressions in Python. We can describe the statement and its embedded conditional expression like this in English:

Print some item if a condition is True, else print some other item.

That was the outside in part. Now the inside out part, for the conditional expression in the print statement:

This is the "some item" part:

chr(ord(it[0][::-1][0]) - 2 ** 5) +
it[0][::-1][(1 >> 8) - (2 ** 8 - 1):]

(For display here, I've removed the backslashes that were there at the ends of some lines, since they are only Python syntax to indicate the continuation of a logical line across physical lines.)

The above "some item" can be evaluated like this:

In each of the 3 iterations of the outer loop, it[0] becomes the string which is the first item of each of the 3 tuples.
So it[0] is successively "\n", "larur ylurt", and "\n".

The chr() function returns the ASCII character for its ASCII code (integer) argument, and the ord() function (the inverse of the chr() function), returns the ASCII code for its ASCII character argument. So chr(65) is 'A' and ord('A') is 65, for example.

Also, in the ASCII code, the uppercase letters A-Z are separated from the corresponding lowercase letters 32 positions each.
That is, ord('t') - ord('T') = 32. The same goes for any other lowercase letter and its corresponding uppercase one.

2 ** 5 is 32.

For a given string s, the expression s[::-1] uses Python string slicing to reverse the string.

1 >> 8 is 256, as is 2 ** 8.

The part x % 2 == 1 makes one part of the conditional expression get evaluated in one case,
when the condition is True ((when ix is 1), and the other part get evaluated in the other two cases,
when the condition is False (when ix is 0 or 2).

bin() is a Python built-in function which converts its argument to binary:

>>> print bin.__doc__
bin(number) -> string

Return the binary representation of an integer or long integer.
The values of bin(4) and bin(1) are as below:
>>> bin(4)
'0b100'
>>> bin(1)
'0b1'
If you need a clue as to why the and operator in the above expression works the way it does, see the below code snippets:
>>> def foo(n):
...     print "in foo, n =", n
...     return n
...
>>> def bar(m):
...     print "in bar, m =", m
...     return m
...
>>> foo(1)
in foo, n = 1
1
>>> bar(2)
in bar, m = 2
2
>>> foo(1) and bar(2)
in foo, n = 1
in bar, m = 2
2
>>> foo(1) or bar(2)
in foo, n = 1
1

The last fragment of the expression for the else part (of the conditional expression), uses string repetition, i.e. the * operator used with a string on the left and an integer on the right, to repeat the string that many times.

Given all the above information, a reader should be able to see (some assembly required) that the program prints the output as shown near the top of the post above, i.e. the string "Truly rural" 5 times, with some number of newlines before and after those 5 lines :)

Here is some background material on conditional expressions in Python:

Python 2 - conditional expressions

Python 3 - conditional expressions

PEP 308

Some examples of the use of conditional expressions, run in the Python shell:

>>> a = 1
>>> b = 1
>>> print 1 if a == b else 2
1
>>> b = 2
>>> print 1 if a == b else 2
2
>>> # A dummy function that considers even-numbered days as fine weather.
...
>>> def fine_weather(day):
...     return day % 2 == 0
...
>>> for i in range(4): print i, fine_weather(i)
...
0 True
1 False
2 True
3 False
>>> for i in range(4): print 'Go out' if fine_weather(i) else 'Stay in'
...
Go out
Stay in
Go out
Stay in

A Python code recipe example for conditional expressions on my ActiveState Code recipes page (over 90 Python recipes there):

Classifying characters using nested conditional expressions

The same code on my blog:

Using nested conditional expressions to classify characters

- Enjoy.


- Vasudev Ram - Online Python training and consulting

I conduct online courses on Python programming, Unix/Linux (commands and shell scripting) and SQL programming and database design, with personal coaching sessions. See the Training page on my blog.

Contact me for details of course content, terms and schedule.

DPD: Digital Publishing for Ebooks and Downloads.

Hit the ground running with my vi quickstart tutorial. I wrote it at the request of two Windows system administrator friends who were given additional charge of some Unix systems. They later told me that it helped them to quickly start using vi to edit text files on Unix.

Check out WP Engine, powerful WordPress hosting.

Get a fast web site with A2 Hosting.

Creating or want to create online products for sale? Check out ConvertKit, email marketing for online creators.

Own a piece of history:
Legendary American Cookware

Teachable: feature-packed course creation platform, with unlimited video, courses and students.

Posts about: Python * DLang * xtopdf

My ActiveState Code recipes

Follow me on:


No comments: