Let us talk a little bit about integer underflow and undefined behaviour in C before we discuss the puzzle I want to share in this post.
#include <stdio.h>
int main()
{
int i;
for (i = 0; i < 6; i--)
printf(".");
return 0;
}
This code invokes undefined behaviour. The value in variable
i
decrements to INT_MIN
after
|INT_MIN| + 1
iterations. In the next iteration, there is a
negative overflow which is undefined for signed integers in C. On many
implementations though, INT_MIN - 1
wraps around to
INT_MAX
. Since INT_MAX
is not less than
6
, the loop terminates. With such implementations, this
code prints print |INT_MIN| + 1
dots. With 32-bit integers,
that amounts to 2147483649 dots. Here is one such example output:
$ gcc -std=c89 -Wall -Wextra -pedantic foo.c && ./a.out | wc -c 2147483649
It is worth noting that the above behaviour is only one of the many
possible ones. The code invokes undefined behaviour and the ISO standard
imposes no requirements on a specific implementation of the compiler
regarding what the behaviour of such code should be. For example, an
implementation could also exploit the undefined behaviour to turn the
loop into an infinite loop. In fact, GCC does optimize it to an infinite
loop if we compile the code with the -O2
option.
# This never terminates! $ gcc -O2 -std=c89 -Wall -Wextra -pedantic foo.c && ./a.out
Let us take a look at the puzzle now.
Add or modify exactly one operator in the following code such that it prints exactly 6 dots.
for (i = 0; i < 6; i--)
printf(".");
An obvious solution is to change i--
to i++
.
for (i = 0; i < 6; i--)
printf(".");
There are a few more solutions to this puzzle. One of the solutions is very interesting. We will discuss the interesting solution in detail below.
Update on 02 Oct 2011: The puzzle has been solved in the comments section. We will discuss the solutions now. If you want to think about the problem before you see the solutions, this is a good time to pause and think about it. There are spoilers ahead.
Here is a list of some solutions:
for (i = 0; i < 6; i++)
for (i = 0; i < 6; ++i)
for (i = 0; -i < 6; i--)
for (i = 0; i + 6; i--)
for (i = 0; i ^= 6; i--)
The last solution involving the bitwise XOR operation is not immediately obvious. A little analysis is required to understand why it works.
Let us generalize the puzzle by replacing \( 6 \) in the loop with an arbitrary positive integer \( n. \) The loop in the last solution now becomes:
for (i = 0; i ^= n; i--)
printf(".");
If we denote the value of the variable i
set by the
execution of i ^= n
after \( k \) dots are printed as
\( f(k), \) then
\[
f(k) =
\begin{cases}
n & \text{if } n = 0, \\
n \oplus (f(k - 1) - 1) & \text{if } n > 1
\end{cases}
\]
where \( k \) is a nonnegative integer, \( n \) is a positive integer,
and the symbol \( \oplus \) denotes bitwise XOR operation on two
nonnegative integers.
Note that \( f(0) \) represents the value of i
set by the
execution of i ^= n
when no dots have been printed yet.
If we can show that \( n \) is the least value of \( k \) for which \( f(k) = 0, \) it would prove that the loop terminates after printing \( n \) dots.
We will see in the next section that for odd values of \( n, \) \[ f(k) = \begin{cases} n & \text{if } k \text{ is even}, \\ 1 & \text{if } k \text{ is odd}. \end{cases} \] Therefore there is no value of \( k \) for which \( f(k) = 0 \) when \( n \) is odd. As a result, the loop never terminates when \( n \) is odd.
We will then see that for even values of \( n \) and \( 0 \leq k \leq n, \) \[ f(k) = 0 \iff k = n. \] Therefore the loop terminates after printing \( n \) dots when \( n \) is even.
We will first prove a few lemmas about some interesting properties of the bitwise XOR operation. We will then use it to prove the claims made in the previous section.
Lemma 1. For an odd positive integer \( n, \) \[ n \oplus (n - 1) = 1 \] where the symbol \( \oplus \) denotes bitwise XOR operation on two nonnegative integers.
Proof. Let the binary representation of \( n \) be \( b_m \dots b_1 b_0 \) where \( m \) is a nonnegative integer and \( b_m \) represents the most significant nonzero bit of \( n. \) Since \( n \) is an odd number, \( b_0 = 1. \) Thus \( n \) may be written as \[ b_m \dots b_1 1. \] As a result \( n - 1 \) may be written as \[ b_m \dots b_1 0. \] The bitwise XOR of both binary representations is \( 1. \)
Lemma 2. For a nonnegative integer \( n, \) \[ n \oplus 1 = \begin{cases} n + 1 & \text{if } n \text{ is even}, \\ n - 1 & \text{if } n \text{ is odd}. \end{cases} \] where the symbol \( \oplus \) denotes bitwise XOR operation on two nonnegative integers.
Proof. Let the binary representation of \( n \) be \( b_m \dots b_1 b_0 \) where \( m \) is a nonnegative integer and \( b_m \) represents the most significant nonzero bit of \( n. \)
If \( n \) is even, \( b_0 = 0. \) In this case, \( n \) may be written as \( b_m \dots b_1 0. \) Thus \( n \oplus 1 \) may be written as \( b_m \dots b_1 1. \) Therefore \( n \oplus 1 = n + 1. \)
If \( n \) is odd, \( b_0 = 1. \) In this case, \( n \) may be written as \( b_m \dots b_1 1. \) Thus \( n \oplus 1 \) may be written as \( b_m \dots b_1 0. \) Therefore \( n \oplus 1 = n - 1. \)
Note that for odd \( n, \) lemma 1 can also be derived as a corollary of lemma 2 in this manner: \[ k \oplus (k - 1) = k \oplus (k \oplus 1) = (k \oplus k) \oplus 1 = 0 \oplus 1 = 1. \]
Lemma 3. If \( x \) is an even nonnegative integer and \( y \) is an odd positive integer, then \( x \oplus y \) is odd, where the symbol \( \oplus \) denotes bitwise XOR operation on two nonnegative integers.
Proof. Let the binary representation of \( x \) be \( b_{xm_x} \dots b_{x1} b_{x0} \) and that of \( y \) be \( b_{ym_y} \dots b_{y1} b_{y0} \) where \( m_x \) and \( m_y \) are nonnegative integers and \( b_{xm_x} \) and \( b_{xm_y} \) represent the most significant nonzero bits of \( x \) and \( y, \) respectively.
Since \( x \) is even, \( b_{x0} = 0. \) Since \( y \) is odd, \( b_{y0} = 1. \)
Let \( z = x \oplus y \) with a binary representation of \( b_{zm_z} \dots b_{z1} b_{z0} \) where \( m_{zm_z} \) is a nonnegative integer and \( b_{zm_z} \) is the most significant nonzero bit of \( z. \)
We get \( b_{z0} = b_{x0} \oplus b_{y0} = 0 \oplus 1 = 1. \) Therefore \( z \) is odd.
Theorem 1. Let \( \oplus \) denote bitwise XOR operation on two nonnegative integers and \[ f(k) = \begin{cases} n & \text{if } n = 0, \\ n \oplus (f(n - 1) - 1) & \text{if } n > 1. \end{cases} \] where \( k \) is a nonnegative integer and \( n \) is an odd positive integer. Then \[ f(k) = \begin{cases} n & \text{if } k \text{ is even}, \\ 1 & \text{if } k \text{ is odd}. \end{cases} \]
Proof. This is a proof by mathematical induction. We have \( f(0) = n \) by definition. Therefore the base case holds good.
Let us assume that \( f(k) = n \) for any even \( k \) (induction hypothesis). Let \( k' = k + 1 \) and \( k'' = k + 2. \)
If \( k \) is even, we get \begin{align*} f(k') & = n \oplus (f(k) - 1) && \text{(by definition)} \\ & = n \oplus (n - 1) && \text{(by induction hypothesis)} \\ & = 1 && \text{(by lemma 1)},\\ f(k'') & = n \oplus (f(k') - 1) && \text{(by definition)} \\ & = n \oplus (1 - 1) && \text{(since \( f(k') = 1 \))} \\ & = n \oplus 0 \\ & = n. \end{align*}
Since \( f(k'') = n \) and \( k'' \) is the next even number after \( k, \) the induction step is complete. The induction step shows that for every even \( k, \) \( f(k) = n \) holds good. It also shows that as a result of \( f(k) = n \) for every even \( k, \) we get \( f(k') = 1 \) for every odd \( k'. \)
Theorem 2. Let \( \oplus \) denote bitwise XOR operation on two nonnegative integers and \[ f(k) = \begin{cases} n & \text{if } n = 0, \\ n \oplus (f(n - 1) - 1) & \text{if } n > 1. \end{cases} \] where \( k \) is a nonnegative integer, \( n \) is an even positive integer, and \( 0 \leq k \leq n. \) Then \[ f(k) = 0 \iff k = n. \]
Proof. We will first show by the principle of mathematical induction that for even \( k, \) \( f(k) = n - k. \) We have \( f(0) = n \) by definition, so the base case holds good. Now let us assume that \( f(k) = n - k \) holds good for any even \( k \) where \( 0 \leq k \leq n \) (induction hypothesis).
Since \( n \) is even (by definition) and \( k \) is even (by induction hypothesis), \( f(k) = n - k \) is even. As a result, \( f(k) - 1 \) is odd. By lemma 3, we conclude that \( f(k + 1) = n \oplus (f(k) - 1) \) is odd.
Now we perform the induction step as follows: \begin{align*} f(k + 2) & = n \oplus (f(k + 1) - 1) && \text{(by definition)} \\ & = n \oplus (f(k + 1) \oplus 1) && \text{(by lemma 2 for odd \( n \))} \\ & = n \oplus ((n \oplus (f(k) - 1)) \oplus 1) && \text{(by definition)} \\ & = (n \oplus n ) \oplus ((f(k) - 1) \oplus 1) && \text{(by associativity of XOR)} \\ & = 0 \oplus ((f(k) - 1) \oplus 1) \\ & = (f(k) - 1) \oplus 1 \\ & = (f(k) - 1) - 1 && \text{(from lemma 2 for odd \( n \))} \\ & = f(k) - 2 \\ & = n - k - 2 && \text{(by induction hypothesis).} \end{align*} This completes the induction step and proves that \( f(k) = n - k \) for even \( k \) where \( 0 \leq k \leq n. \)
We have shown above that \( f(k) \) is even for every even \( k \) where \( 0 \leq k \leq n \) which results in \( f(k + 1) \) as odd for every odd \( k + 1. \) This means that \( f(k) \) cannot be \( 0 \) for any odd \( k. \) Therefore \( f(k) = 0 \) is possible only even \( k. \) Solving \( f(k) = n - k = 0, \) we conclude that \( f(k) = 0 \) if and only if \( k = n. \)
]]>A few weeks ago, I watched Rise of the Planet of the Apes. The movie showed a genetically engineered chimpanzee trying to solve a puzzle involving four discs, initially stacked in ascending order of size on one of three pegs. The chimpanzee was supposed to transfer the entire stack to one of the other pegs, moving only one disc at a time, and never placing a larger disc on a smaller one.
The problem was called the Lucas' Tower in the movie. I have always known this problem as the Tower of Hanoi puzzle. The minimum number of moves required to solve the problem is \( 2^n - 1 \) where \( n \) is the number of discs. In the movie, the chimpanzee solved the problem in 15 moves, the minimum number of moves required when there are 4 discs.
Referring to the problem as the Lucas' Tower made me wonder why it was called so instead of calling it the Tower of Hanoi. I guessed it was probably because the puzzle was invented by the French mathematician Édouard Lucas. Later when I checked the Wikipedia article on this topic, I realized I was right about this. In fact, the article mentioned that there is another version of this problem known as the Tower of Brahma that involves 64 discs made of pure gold and three diamond needles. According to a legend, a group of Brahmin priests are working at the problem and the world will end when the last move of the puzzle is completed. Now, even if they make one move every second, it'll take 18 446 744 073 709 551 615 seconds to complete the puzzle. That's about 585 billion years. The article also had this nice animation of a solution involving four discs.
I'll not discuss the solution of this puzzle in this blog post. There are plenty of articles on the web including the Wikpedia article that describes why it takes a minimum of \( 2^n - 1 \) moves to solve the puzzle when there are \( n \) discs involved. In this post, I'll talk about an interesting result I discovered while playing with this puzzle one afternoon.
If we denote the minimum number of moves required to solve the Tower of Hanoi puzzle as \( T_n, \) then \( T_n \) when expressed in binary is the largest possible \( n \)-bit integer. For example, \( T_4 = 15_{10} = 1111_{2}. \) That makes sense because \( T_n = 2^n - 1 \) indeed represents the maximum possible \( n \)-bit integer where all \( n \) bits are set to \( 1. \)
While playing with different values of \( T_n \) for different values of \( n, \) I stumbled upon an interesting result which I will pose as a problem in a later section below.
Before proceeding to the problem, let us define the bit-length of an integer to eliminate any possibility of ambiguity:
We will be dealing with arbitrary precision integers (bignums) in the problem, so let us also make a few assumptions:
The definition along with the assumptions lead to the following conclusions:
The naive approach involves adding all the \( n \) integers and counting the number of \( 1 \)-bits in the sum. It takes \( O(n^2) \) time to add the \( n \) integers. The sum is an \( (n + 1) \)-bit integer, so it takes \( O(n) \) time to count the number of \( 1 \)-bits in the sum. Since the sum is \( (n + 1) \)-bit long, it takes \( O(n) \) memory to store the sum. If \( n \) is as large as, say, \( 2^{64}, \) it takes 2 exbibytes plus one more bit of memory to store the sum.
We can arrive at a much more efficient solution if we look at what the binary representation of the sum looks like. We first arrive at a closed-form expression for the sum: \begin{align*} T_1 + T_2 + \dots + T_n & = (2 - 1) + (2^2 - 1) + \dots + (2^n - 1) \\ & = (2 + 2^2 + \dots + 2^n) - n \\ & = (2^{n + 1} - 2) - n \\ & = (2^{n + 1} - 1) - (n + 1). \end{align*}
Now \( 2^{n + 1} - 1 \) is an \( (n + 1) \)-bit number with all its bits set to \( 1. \) Subtracting \( n + 1 \) from it is equivalent to performing the following operation with their binary representations: for each \( 1 \)-bit in \( (n + 1), \) set the corresponding bit in \( (2^{n + 1} - 1) \) to \( 0. \)
If we use the notation \( \text{bitcount}(n) \) to represent the number of \( 1 \)-bits in the binary representation of a positive integer \( n, \) then we get \[ \text{bitcount}(T_1 + T_2 + \dots + T_n) = (n + 1) - \text{bitcount}(n + 1). \]
Now the computation involves counting the number of \( 1 \)-bits in \( n + 1 \) which takes \( O(\log n) \) and subtracting this count from \( n + 1 \) which also takes \( O(\log n) \) time. Further, the largest number that we keep in memory is \( n + 1 \) which occupies \( O(\log n) \) space. Therefore, the entire problem can be solved in \( O(\log n) \) time with \( O(\log n) \) space.
What would have taken 2 exbibytes and 1 bit of memory with the naive approach requires 8 bytes and 1 bit of memory now.
]]>A few days ago, I came across this problem:
There is a sequence of \( 2n \) numbers where each natural number from \( 1 \) to \( n \) is repeated twice, i.e., \[ (1, 1, 2, 2, \dots, n, n). \] Find a permutation of this sequence such that for each \( k \) where \( 1 \le k \le n, \) there are \( k \) numbers between two occurrences of \( k \) in the permutation.
In combinatorics, this problem has a name: Langford's problem. A permutation of \( (1, 1, 2, 2, \dots, n, n) \) that satisfies the condition given in the probem is known as a Langford pairing or Langford sequence.
For small \( n, \) say \( n = 4, \) Langford pairings can be obtained easily by trial and error: \( (4, 1, 3, 1, 2, 4, 3, 2). \) What if \( n \) is large? We need an algorithm to find a permutation that solves the problem in that case.
There is another question to consider: Is there a solution for every possible \( n? \) One can easily see that there are no Langford pairings for \( n = 1 \) and \( n = 2, \) i.e., the sequences \( (1, 1) \) and \( (1, 1, 2, 2) \) have no Langford pairings.
We need to understand two things:
A simple Python 3 program I wrote to find Langford pairings for small values of \( n \) offers some clues. Here is the program:
def find_solutions(n, s=None):
# If called from main(), i.e., if called without the s parameter,
# create a list of 2n zero values. Zeroes represent unoccupied cells.
if s is None:
s = [0] * (2 * n)
# The next number to be placed in two cells.
m = max(s) + 1
# For each i, try to place m at s[i] and s[i + m + 1].
for i in range(2 * n - m - 1):
# If s[i] and s[i + m + 1] are unoccupied.
if s[i] == s[i + m + 1] == 0:
# Create a copy of the list so that the original list is
# unaffected.
s_copy = s[:]
s_copy[i] = s_copy[i + m + 1] = m
# If this number is the last number to be placed, ...
if m == n:
# then a solution has been found; yield it.
yield s_copy
else:
# else try to place the next number.
yield from find_solutions(n, s_copy)
# Count solutions for 1 <= n <= 12.
for n in range(1, 13):
solutions = list(find_solutions(n))
print('n = {:2} => {:6} solutions'.format(n, len(solutions)))
Depending on your CPU, it can take a couple minutes to about ten minutes for this program to run. Here is the output from the above program:
$ python3 langford.py n = 1 => 0 solutions n = 2 => 0 solutions n = 3 => 2 solutions n = 4 => 2 solutions n = 5 => 0 solutions n = 6 => 0 solutions n = 7 => 52 solutions n = 8 => 300 solutions n = 9 => 0 solutions n = 10 => 0 solutions n = 11 => 35584 solutions n = 12 => 216288 solutions
Note that we always talk about Langford pairings in plural in this post. That's because either a sequence has no Langford pairings or it has two or more Langford pairings. There is never a sequence that has only one Langford pairing. That's because if we find at least one Langford pairing for a sequence, the reverse of that Langford pairing is also a Langford pairing. Therefore, when Langford pairings exist for a sequence, they must at least be two in number. Since they occur in pairs, they are always even in number. This is why we don't have to write "one or more Langford pairings" in this post. We can always write "Langford pairings" instead.
From the output above, we can form a conjecture:
For convenience, let us denote the sequence \( (1, 1, 2, 2, \dots, n, n) \) as \( S_n. \) We will now prove the above conjecture in two parts:
Let \( S_n = (1, 1, 2, 2, \dots, n, n) \) be a sequence such that it has Langford pairings. Let us pick an arbitrary Langford pairing \( s \) of \( S_n \) and split this Langford pairing \( s \) into two mutually exclusive subsequences \( s_1 \) and \( s_2 \) such that:
For example, if we pick \( s = (1, 7, 1, 2, 5, 6, 2, 3, 4, 7, 5, 3, 6, 4) \) which is a Langford pairing of \( S_7, \) we split \( s \) into \begin{align*} s_1 & = (1, 1, 5, 2, 4, 5, 6), \\ s_2 & = (7, 2, 6, 3, 7, 3, 4). \end{align*}
We can make a few observations:
Do these observations hold good for every Langford pairing of any aribrary \( S_n \) for every positive integer value of \( n? \) Yes, they do. We will now prove them one by one:
Let us consider an even number \( k \) from a Langford pairing. If the first occurrence of \( k \) lies at the \( i \)th position in the pairing, then its second occurrence lies at the \( (i + k + 1) \)th position. Since \( k \) is even, \( i \) and \( i + k + 1 \) have different parities, i.e., if \( i \) is odd, then \( i + k + 1 \) is even and vice versa. Therefore, if the first occurrence of \( k \) lies at an odd numbered position, its second occurrence must lie at an even numbered position and vice versa. Thus one occurrence of \( k \) must belong to \( s_1 \) and the other must belong to \( s_2. \) This proves the first observation.
The number of even numbers between \( 1 \) and \( n, \) inclusive, is \( \left\lfloor \frac{n}{2} \right\rfloor. \) Each of these even numbers has been split equally between \( s_1 \) and \( s_2. \) This proves the second observation.
Now let us consider an odd number \( k \) from a Langford pairing. If the first occurrence of \( k \) lies at the \( i \)th position in the pairing, then its second occurrence lies at the \( (i + k + 1) \)th position. Since \( k \) is odd, \( i \) and \( i + k + 1 \) have the same parity. Therefore, either both occurrences of \( k \) belong to \( s_1 \) or both belong to \( s_2. \) This proves the third observation.
Each subsequence, \( s_1 \) or \( s_2 \) has \( n \) numbers because we split a Langford pairing \( s \) with \( 2n \) numbers equally between the two subsequences. We have shown that each subsequence has \( \left\lfloor \frac{n}{2} \right\rfloor \) even numbers. Therefore the number of odd numbers in each subsequence is \( n - \left\lfloor \frac{n}{2} \right\rfloor = \left\lceil \frac{n}{2} \right\rceil. \)
From the third observation, we know that the odd numbers always occur in pairs in each subsequence because both occurrences of an odd number occur together in the same subsequence. Therefore, the number of odd numbers in each subsequence must be even. Since the number of odd numbers in each subsequence is \( \left\lceil \frac{n}{2} \right\rceil \) as proven for the fourth observation, we conclude that \( \left\lceil \frac{n}{2} \right\rceil \) must be even.
Now let us see what must \( n \) be like so that \( \left\lceil \frac{n}{2} \right\rceil \) is even.
Let us express \( n \) as \( 4q + r \) where \( q \) is a nonnegative integer and \( r \in \{0, 1, 2, 3\}. \)
We see that \( \left\lceil \frac{n}{2} \right\rceil \) is even if and only if either \( n \equiv 0 \pmod{4} \) or \( n \equiv 3 \pmod{4} \) holds good.
We have shown that if a sequence \( S_n \) has Langford pairings, then either \( n \equiv 0 \pmod{4} \) or \( n \equiv 3 \pmod{4}. \) This proves the necessity of the condition.
If we can show that we can construct a Langford pairing for \( (1, 1, 2, 2, \dots, n, n ) \) for both cases, i.e., \( n \equiv 0 \pmod{4} \) as well as \( n \equiv 3 \pmod{4}, \) then it would complete the proof.
Let us define some notation to make it easier to write sequences we will use in the construction of a Langford pairing:
\( (i \dots j)_{even} \) denotes a sequence of even positive integers from \( i \) to \( j, \) exclusive, arranged in ascending order.
For example, \( (1 \dots 8)_{even} = (2, 4, 6) \) and \( (1 \dots 2)_{even} = (). \)
\( (i \dots j)_{odd} \) denotes a sequence of odd positive integers from \( i \) to \( j, \) exclusive, arranged in ascending order.
For example, \( (1 \dots 8)_{odd} = (3, 5, 7) \) and \( (1 \dots 3)_{odd} = (). \)
\( s' \) denotes the reverse of the sequence \( s. \)
For example, for a sequence \( s = (2, 3, 4, 5), \) we have \( s' = (2, 3, 4, 5)' = (5, 4, 3, 2). \)
\( s \cdot t \) denotes the concatenation of sequences \( s \) and \( t. \)
For example, for sequences \( s = (1, 2, 3) \) and \( t = (4, 5) , \) we have \( s \cdot t = (1, 2, 3) \cdot (4, 5) = (1, 2, 3, 4, 5). \)
Let \( x = \left\lceil \frac{n}{4} \right\rceil. \) Therefore, \[ x = \begin{cases} \frac{n}{4} & \text{if } n \equiv 0 \pmod{4}, \\ \frac{n + 1}{4} & \text{if } n \equiv 3 \pmod{4}. \end{cases} \] Let us now define the following eight sequences: \begin{align*} a & = (2x - 1), \\ b & = (4x - 2), \\ c & = (4x - 3), \\ d & = (4x), \\ p & = (0 \dots a)_{odd}, \\ q & = (0 \dots a)_{even}, \\ r & = (a \dots b)_{odd}, \\ s & = (a \dots b)_{even}. \end{align*} Now let us construct a Langford pairing for both cases: \( n \equiv 0 \pmod{4} \) and \( n \equiv 3 \pmod{4}. \) We will do this case by case.
If \( n \equiv 0 \pmod{4}, \) we construct a Langford pairing with the following concatenation: \[ s' \cdot p' \cdot b \cdot p \cdot c \cdot s \cdot d \cdot r' \cdot q' \cdot b \cdot a \cdot q \cdot c \cdot r \cdot a \cdot d. \] Let us do an example with \( n = 12. \)
For \( n = 12, \) we get \( x = \frac{n}{4} = 3. \) Therefore, \begin{alignat*}{2} a & = (2x - 1) && = (5), \\ b & = (4x - 2) && = (10), \\ c & = (4x - 3) && = (11), \\ d & = (4x) && = (12), \\ p & = (0 \dots a)_{odd} && = (1, 3), \\ q & = (0 \dots a)_{even} && = (2, 4), \\ r & = (a \dots b)_{odd} && = (7, 9), \\ s & = (a \dots b)_{even} && = (6, 8). \end{alignat*} After performing the specified concatenation, we get the following Langford pairing: \[ ( 8, 6, 3, 1, 10, 1, 3, 11, 6, 8, 12, 9, 7, 4, 2, 10, 5, 2, 4, 11, 7, 9, 5, 12 ). \] Let us now show that any construction of a sequence as per this specified concatenation always leads to a Langford pairing.
Each sequence \( a, \) \( b, \) \( c, \) and \( d \) has one number each. Each sequence \( p, \) \( q, \) \( r, \) and \( s \) has \( x - 1 \) numbers each.
The two occurrences of \( a \) have \( q, \) \( c, \) and \( r \) in between, i.e., \( (x - 1) + 1 + (x - 1) = 2x - 1 = a \) numbers in between. Similarly, we can check that the two occurrences of \( b \) have \( b \) numbers in between, likewise for \( c \) and \( d. \)
The two occurrences of \( 1 \) belong to belong to \( p. \) Between these two occurrences of \( 1, \) we have only one element of \( b. \) We can show that for each \( k \) in \( p, \) there are \( k \) numbers in between. For any \( k \) in \( p, \) there is the sequence \( (0..k)'_{odd} \cdot b \cdot (0..k)_{odd} \) in between the two occurrences of \( k, \) i.e, there are \( \frac{k - 1}{2} + 1 + \frac{k - 1}{2} = k \) numbers in between.
If \( n \equiv 3 \pmod{4}, \) we construct a Langford pairing with the following concatenation: \[ s' \cdot p' \cdot b \cdot p \cdot c \cdot s \cdot a \cdot r' \cdot q' \cdot b \cdot a \cdot q \cdot c \cdot r. \] Note that this concatenation of sequences is almost the same as the concatenation in the previous section with the following two differences:
Let us do an example with \( n = 11. \) For \( n = 12, \) we get \( x = \frac{n + 1}{4} = 3. \) Therefore, the sequences \( a, \) \( b, \) \( c, \) \( p, \) \( q, \) \( r, \) and \( s \) are same as those in the last example in the previous section. After performing the specified concatenation, we get the following Langford pairing: \[ (8, 6, 3, 1, 10, 1, 3, 11, 6, 8, 5, 9, 7, 4, 2, 10, 5, 2, 4, 11, 7, 9). \]
We can verify that for every \( k \) in a Langford pairing constructed in this manner, there are \( k \) numbers in between. The verification steps are similar to what we did in the previous section.
]]>#include <stdio.h>
int main()
{
https://susam.net/
printf("hello, world\n");
return 0;
}
This code compiles and runs successfully.
$ c99 hello.c && ./a.out hello, world
However, the C99 standard does not mention anywhere that a URL is a valid syntactic element in C. How does this code work then?
Update on 04 Jun 2011: The puzzle has been solved in the comments section. If you want to think about the problem before you see the solutions, this is a good time to pause and think about it. There are spoilers ahead.
The code works fine because https:
is a label and
//
following it begins a comment. In case, you are
wondering if //
is indeed a valid comment in C, yes, it
is, since C99. Download the
C99
standard, go to section 6.4.9 (Comments) and read the second
point which mentions this:
Except within a character constant, a string literal, or a comment,
the characters //
introduce a comment that includes all
multibyte characters up to, but not including, the next new-line
character. The contents of such a comment are examined only to
identify multibyte characters and to find the terminating new-line
character.
]]>
A perfect riffle shuffle of a deck of cards involves splitting the deck into two halves (one in each hand) and then alternately dropping one card from each half till all cards have fallen. If the shuffling is done in such a manner that the bottom and the top cards remain preserved at their positions, then it is an out shuffle.
Here is a problem that a colleague asked me recently while discussing shuffling techniques:
The solution to this problem is rather long, so it has been split into three parts below.
Let us assume that there are \( n \) cards where \( n \) is a positive even integer. Let us denote these cards as \( c_0, c_1, \dots, c_{n-1} \) where \( c_0 \) is the card at index \( 0 \) (top of the deck), \( c_{n - 1} \) is the card at index \( n - 1 \) (bottom of the deck), and \( c_i \) is the card at index \( i \) where \( 0 \le i \le n - 1. \)
For example, if we have \( 8 \) cards, then the cards are denoted as \[ c_0, c_1, c_2, c_3, c_4, c_5, c_6, c_7. \] After the first out shuffle, these cards are now in this order: \[ c_0, c_4, c_1, c_5, c_2, c_6, c_3, c_7. \]
From the problem description and the example above, we see that after a single out shuffle, a card at index \( i \) moves to index \( 2i \bmod (n - 1) \) for \( 0 \le i < n - 1 \) and the card at index \( n - 1 \) remains at the same place.
After \( m \) shuffles a card at index \( i \) moves to index \( (2^m i) \bmod (n - 1) \) for \( 0 \le i < n - 1. \) The card at index \( n - 1 \) always remains at the same place.
The solution to the problem then is the smallest positive integer \( m \) such that \[ 2^m i \equiv i \pmod{n - 1}. \] for all integers \( i \) that satisfy the inequality \( 0 \le i < n - 1. \)
In modular arithmetic, we know that \[ ac \equiv bc \pmod{n} \iff a \equiv b \pmod{n / \gcd(c, n)}. \] where \( a, \) \( b, \) \( c, \) and \( n \) are integers. Therefore \[ 2^m i \equiv i \pmod{n - 1} \iff 2^m \equiv 1 \pmod{(n - 1) / \gcd(i, n - 1)}. \] Let \( F_{ni} = \frac{n - 1}{\gcd(i, n - 1)} \) where \( 0 \le i < n - 1. \) Now the above congruence relation can be written as \[ 2^m \equiv 1 \pmod{F_{ni}}. \]
The smallest positive integer \( m \) that satisfies the above congruence relation is known as the multiplicative order of \( 2 \) modulo \( F_{ni}. \) It is denoted as \( \ord_{F_{ni}}(2). \)
In general, given an integer \( a \) and a positive integer \( n \) with \( \gcd(a, n) = 1, \) the multiplicative order of \( a \) modulo \( n,\) denoted as \( \ord_{n}(a), \) is the smallest positive integer \( k \) such that \[ a^k \equiv 1 \pmod{n}. \] In this problem, \( n \) is even, so \( n - 1 \) is odd as a result of which \( F_{ni} \) is also odd. As a result, \( \gcd(2, F_{ni}) = 1. \) Therefore, the smallest positive integer \( m \) that satisfies the congruence relation \( 2^m \equiv 1 \pmod{F_{ni}} \) is denoted as \( \ord_{F_{ni}}(2). \)
If \( n = 2, \) \( F_{n0} = F_{n1} = 1, \) therefore \( 2^m \equiv 1 \pmod{F_{ni}} \) for \( 0 \le i < n - 1 \) and all positive integers \( m. \) This proves the trivial observation that when there are only two cards \( c_0 \) and \( c_1, \) they remain at index \( 0 \) and index \( 1, \) respectively, after any number of out shuffles, i.e., their positions do not change with out shuffles.
If \( n \ge 4, \) we know that there exists at least one integer between \( 1 \) and \( n - 1 \) that is coprime to \( n - 1 \) because \( \varphi(n) \ge 2 \) for \( n \ge 4 \) where \( \varphi(n) \) represents Euler's totient of \( n. \)
For any arbitrary \( n \ge 4, \) let \( i \) be an integer that is coprime to \( n - 1 \) such that \( 1 < i < n - 1. \) Now \( \gcd(i, n - 1 ) = 1, \) so \( F_{ni} = \frac{n - 1}{\gcd(i, n - 1)} = n - 1. \) As a result, \( \ord_{F_{ni}}(2) = \ord_{n - 1}({2}). \) This shows that any card at index \( i \) such that \( i \) is coprime to \( n - 1 \) requires a minimum of \( \ord_{n - 1}(2) \) out shuffles to return to its initial place.
For any arbitrary \( n \ge 4, \) now let us consider the case of a card at index \( i \) such that \( i \) is not coprime to \( n - 1. \) Since \( n - 1 \) is odd, we have \( \gcd(2, n - 1) = 1. \) Therefore, by definition of multiplicative order, \[ 2^{\ord_{n - 1}(2)} \equiv 1 \pmod{n - 1}. \] Since \( F_{ni} \mid n - 1, \) we get, \[ 2^{\ord_{n - 1}(2)} \equiv 1 \pmod{F_{ni}}. \] This shows that a card at index \( i \) such that \( i \) is not coprime to \( n - 1 \) also return to its initial place after \( \ord_{n - 1}(2) \) out shuffles. Actually, it takes only \( \ord_{F_{ni}}(2) \) out shuffles to return the card to its initial place. But \( \ord_{F_{ni}}(2) \mid \ord_{n - 1}(2), \) so \( \ord_{n - 1}(2) = c \ord_{F_{ni}}(2) \) for some positive integer \( c. \) Therefore performing \( \ord_{n - 1}(2) \) out shuffles is same as repeating \( \ord_{F_{ni}}(2) \) out shuffles \( c \) times. Every \( \ord_{F_{ni}}(2) \) brings back the card to its initial position, so repeating it \( c \) times also brings it back to its initial position.
We have shown that a card at index \( i \) such that \( i \) is coprime to \( n - 1 \) takes a minimum of \( \ord_{n - 1}(2) \) out shuffles to return to its initial place. We have also shown that a card at other indices also return to its initial place after the same number of out shuffles. Therefore, it takes a minimum of \( \ord_{n - 1}{2} \) out shuffles to return the deck of cards to its initial state.
When there are only \( 2 \) cards in the deck, every out shuffle trivially returns the deck to its initial state. In other words, it takes only \( 1 \) out shuffle to return the deck to its initial state. Indeed \( \ord_{n - 1}(2) = \ord_{1}(2) = 1. \)
We have now shown that for any positive even integer \( n, \) a deck of \( n \) cards returns to its initial state after \( \ord_{n - 1}(2) \) out shuffles.
For a deck of \( 52 \) cards, we have \( n = 52. \) A minimum of \( \ord_{51}(2) \) out shuffles are required to return the deck to its initial state. To compute \( \ord_{51}(2) \) we first enumerate the powers of \( 2 \) modulo \( 51 \): \begin{alignat*}{2} 2^0 & \equiv 1 && \pmod{51}, \\ 2^1 & \equiv 2 && \pmod{51}, \\ 2^2 & \equiv 4 && \pmod{51}, \\ 2^3 & \equiv 8 && \pmod{51}, \\ 2^4 & \equiv 16 && \pmod{51}, \\ 2^5 & \equiv 32 && \pmod{51}, \\ 2^6 & \equiv 13 && \pmod{51}, \\ 2^7 & \equiv 26 && \pmod{51}, \\ 2^8 & \equiv 1 && \pmod{51}. \end{alignat*} The smallest positive integer \( k \) such that \( 2^k \equiv 1 \pmod{51} \) is 8, so \( \ord_51(2) = 8. \) We need \( 8 \) out shuffles to return a deck of \( 52 \) cards to its initial state.
]]>If you want to think about this puzzle, this is a good time to pause and think about it. There are spoilers ahead.
It does not take long to realize that this is a Diophantine equation of the form \( a^n + b^n = c^n. \) Here is how the equation looks after rearranging the terms: \[ x^3 = 18y^2 + 54. \]
The right hand side is positive, so any \( x \) that satisfies this equation must also be positive, i.e., \( x > 0 \) must hold good for any solution \( x \) and \( y. \)
Also, if some \( y \) satisfies the equation, then \( -y \) also satisfies the equation because the right hand side value remains the same for both \( y \) and \( -y. \)
The right hand side is \( 2(9y^2 + 3^3). \) This is of the form \( 2(3a^2b + b^3) \) where \( a = y \) and \( b = 3. \) Now \( 2(3a^2b + b^3) = (a + b)^3 - (a - b)^3. \) Using these details, we get \[ x^3 = 18y^2 + 54 = 2(9y^2 + 3^3) = (y + 3)^3 - (y - 3)^3. \] Rearranging the terms, we get \[ x^3 + (y - 3)^3 = (y + 3)^3. \] From Fermat's Last Theorem, we know that an equation of the form \( a^n + b^n = c^n \) does not have any solution for positive integers \( a, \) \( b, \) \( c, \) and positive integer \( n > 2. \) Therefore we arrive at the following inequalities for any \( x \) and \( y \) that satisfy the equation: \begin{align*} x > 0, \\ -3 \le y \le 3. \end{align*} We established the first inequality earlier when we discussed that \( x \) must be positive. The second inequality follows from Fermat's Last Theorem. If there were a solution \( x \) and \( y \) such that \( x > 0 \) and \( y > 3, \) then \( x^3 + (y - 3)^3 = (y + 3)^3 \) would contradict Fermat's Last Theorem. Therefore the inequality \( y \le 3 \) must hold good. Further since for every solution \( x \) and \( y, \) there is also a solution \( x \) and \( -y, \) the inequality \( -y \le 3 \) must also hold good.
Since \( y \) must be one of the seven integers between \( -3 \) and \( 3, \) inclusive, we can try solving for \( x \) with each of these seven values of \( y. \) When we do so, we find that there are only two values of \( y \) for which we get integer solutions for \( x. \) They are \( y = 3 \) and \( y = -3. \) In both cases, we get \( x = 6. \) Therefore, the solutions to the given equation are: \[ x = 6, \qquad y = \pm 3. \]
]]>Here is a C puzzle that involves some analysis of the machine code generated from it followed by manipulation of the runtime stack. The solution to this puzzle is implementation-dependent. Here is the puzzle:
Consider this C code:
#include <stdio.h>
void f()
{
}
int main()
{
printf("1\n");
f();
printf("2\n");
printf("3\n");
return 0;
}
Define the function f()
such that the output of the
above code is:
1
3
Printing 3
in f()
and exiting is not
allowed as a solution.
If you want to think about this problem, this is a good time to pause and think about it. There are spoilers ahead.
The solution essentially involves figuring out what code we can
place in the body of f()
such that it causes the
program to skip over the machine code generated for
the printf("2\n")
operation. I'll share two solutions
for two different implementations:
Let us first see step by step how I approached this problem for GCC.
We add a statement char a = 7;
to the function
f()
. The code looks like this:
#include <stdio.h>
void f()
{
char a = 7;
}
int main()
{
printf("1\n");
f();
printf("2\n");
printf("3\n");
return 0;
}
There is nothing special about the number 7
here. We
just want to define a variable in f()
and assign some
value to it.
Then we compile the code and analyze the machine code generated for
f()
and main()
functions.
$ gcc -c overwrite.c && objdump -d overwrite.o overwrite.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <f>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: c6 45 ff 07 movb $0x7,-0x1(%rbp) 8: c9 leaveq 9: c3 retq 000000000000000a <main>: a: 55 push %rbp b: 48 89 e5 mov %rsp,%rbp e: bf 00 00 00 00 mov $0x0,%edi 13: e8 00 00 00 00 callq 18 <main+0xe> 18: b8 00 00 00 00 mov $0x0,%eax 1d: e8 00 00 00 00 callq 22 <main+0x18> 22: bf 00 00 00 00 mov $0x0,%edi 27: e8 00 00 00 00 callq 2c <main+0x22> 2c: bf 00 00 00 00 mov $0x0,%edi 31: e8 00 00 00 00 callq 36 <main+0x2c> 36: b8 00 00 00 00 mov $0x0,%eax 3b: c9 leaveq 3c: c3 retq
When main()
calls f()
, the microprocessor
saves the return address (where the control must return to after
f()
is executed) in stack. The line at offset
1d in the listing above for main()
is the
call to f()
. After f()
is executed, the
instruction at offset 22 is executed. Therefore the
return address that is saved on stack is the address at which the
instruction at offset
22 would be present at runtime.
The instructions at offsets 22 and 27 are
the instructions for the printf("2\n")
call. These are
the instructions we want to skip over. In other words, we want to
modify the return address in the stack from the address of the
instruction at offset 22 to that of the instruction at
offset 2c. This is equivalent to skipping 10 bytes
(0x2c - 0x22 = 10) of machine code or adding 10 to the return
address saved in the stack.
Now how do we get hold of the return address saved in the stack when
f()
is being executed? This is where the variable
a
we defined in f()
helps. The instruction
at offset 4 is the instruction generated for
assigning 7
to the variable a
.
From the knowledge of how microprocessor works and from the machine
code generated for f()
, we find that the following
sequence of steps are performed during the call to f()
:
f()
pushes the content of the RBP (base
pointer) register into the stack.
f()
copies the content of the RSP (stack
pointer) register to the RBP register.
f()
stores the byte value 7
at the memory address specified by the content of RBP minus 1.
This achieves the assignment of the value 7
to the
variable a
.
After 7
is assigned to the variable a
, the
stack is in the following state:
Address | Content | Size (in bytes) |
---|---|---|
&a + 5 |
Return address (old RIP) | 8 |
&a + 1 |
Old base pointer (old RBP) | 8 |
&a |
Variable a |
1 |
If we add 9 to the address of the variable a
, i.e.,
&a
, we get the address where the return address is
stored. We saw earlier that if we increment this return address by
10 bytes, it solves the problem. Therefore here is the solution
code:
#include <stdio.h>
void f()
{
char a;
(&a)[9] += 10;
}
int main()
{
printf("1\n");
f();
printf("2\n");
printf("3\n");
return 0;
}
Finally, we compile and run this code and confirm that the solution works fine:
$ gcc overwrite.c && ./a.out 1 3
Now we will see another example solution, this time for Visual Studio 2005.
Like before we define a variable a
in f()
.
The code now looks like this:
#include <stdio.h>
void f()
{
char a = 7;
}
int main()
{
printf("1\n");
f();
printf("2\n");
printf("3\n");
return 0;
}
Then we compile the code and analyze the machine code generated from it.
C:\>cl overwrite.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. overwrite.c Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:overwrite.exe overwrite.obj C:\>dumpbin /disasm overwrite.obj Microsoft (R) COFF/PE Dumper Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file overwrite.obj File Type: COFF OBJECT _f: 00000000: 55 push ebp 00000001: 8B EC mov ebp,esp 00000003: 51 push ecx 00000004: C6 45 FF 07 mov byte ptr [ebp-1],7 00000008: 8B E5 mov esp,ebp 0000000A: 5D pop ebp 0000000B: C3 ret 0000000C: CC int 3 0000000D: CC int 3 0000000E: CC int 3 0000000F: CC int 3 _main: 00000010: 55 push ebp 00000011: 8B EC mov ebp,esp 00000013: 68 00 00 00 00 push offset $SG2224 00000018: E8 00 00 00 00 call _printf 0000001D: 83 C4 04 add esp,4 00000020: E8 00 00 00 00 call _f 00000025: 68 00 00 00 00 push offset $SG2225 0000002A: E8 00 00 00 00 call _printf 0000002F: 83 C4 04 add esp,4 00000032: 68 00 00 00 00 push offset $SG2226 00000037: E8 00 00 00 00 call _printf 0000003C: 83 C4 04 add esp,4 0000003F: 33 C0 xor eax,eax 00000041: 5D pop ebp 00000042: C3 ret Summary B .data 57 .debug$S 2F .drectve 43 .text
Just like in the previous objdump
listing, in this
listing too, the instruction at offset 4
shows where
the variable a
is allocated and the instructions at
offsets 25
, 2A
, and 2F
show
the instructions we want to skip, i.e., instead of returning to the
instruction at offset 25
, we want the microprocessor to
return to the instruction at offset 32
. This involves
skipping 13 bytes (0x32 - 0x25 = 13) of machine code.
Unlike the previous objdump
listing, in this listing we
see that the Visual Studio I am using is a 32-bit on, so it
generates machine code to use 32-bit registers like EBP, ESP, etc.
Thus the stack looks like this after 7
is assigned to
the variable
a
:
Address | Content | Size (in bytes) |
---|---|---|
&a + 5 |
Return address (old EIP) | 4 |
&a + 1 |
Old base pointer (old EBP) | 4 |
&a |
Variable a |
1 |
If we add 5 to the address of the variable a
, i.e.,
&a
, we get the address where the return address is
stored. Here is the solution code:
#include <stdio.h>
void f()
{
char a;
(&a)[5] += 13;
}
int main()
{
printf("1\n");
f();
printf("2\n");
printf("3\n");
return 0;
}
Finally, we compile and run this code and confirm that the solution works fine:
C:\>cl /w overwrite.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. overwrite.c Microsoft (R) Incremental Linker Version 8.00.50727.42 Copyright (C) Microsoft Corporation. All rights reserved. /out:overwrite.exe overwrite.obj C:\>overwrite.exe 1 3
The machine code that the compiler generates for a given C code is highly dependent on the implementation of the compiler. In the two examples above, we have two different solutions for two different compilers.
Even with the same brand of compiler, the way it generates machine code for a given code may change from one version of the compiler to another. Therefore, it is very likely that the above solution would not work on another system (such as your system) even if you use the same compiler that I am using in the examples above.
However, we can arrive at the solution for an implementation of the
compiler by determining what number to add to &a
to get
the address where the return address is saved on stack and what number
to add to this return address to make it point to the instruction we
want to skip to after f()
returns.
If the factory were using a beam balance, this would have been an easy problem to solve. The workers could then place the required weight in one pan and keep pouring the material in the other pan until the beam appears to be balanced.
However this factory uses a digital weighing scale which involves recognizing decimal digits to read the measurement. What do you think is the easiest way to train the workers to do their job without having to teach them how to count?
I have a solution in mind which would involve configuring the digital weighing scale in a certain way for every measurement the workers have to make. I will propose this solution to the management team of the factory. But I would like to know any suggestions you have and take them into account as well before proposing my solution. I will also share the solution I have in mind after learning about your suggestions.
Update on 08 July 2010: Prunthaban has suggested a solution in the comments section which is pretty much what I had in mind too. I will propose this solution to the factory. The solution works like this: Say the worker has to fill 5.3 kg of a certain material into the bucket. A literate supervisor first places 5.3 kg of that material on the weighing scale. The instrument now shows "5.3 kg" on its display. Now the remainder of the solution assumes that the digital weighing balance has a calibration wheel to calibrate the instrument. The supervisor turns the calibration wheel until the display shows "0.0" kg while the 5.3 kg of material is still placed on it. Now the material is removed from the balance and the display shows "-5.3 kg". Now all a worker needs to do is pour the given material on this calibrated balance until the display shows "0.0 kg". As long as the workers can be taught to recognize the digit "0", this solution should work fine.
Update on 05 Aug 2010: The above solution was successfully implemented in OCL India Limited, the silica bricks factory where this problem was faced. The digital weighing balance involved in the problem indeed had a calibration wheel, so the above solution worked for them. Thank you, Prunthaban and others who participated in this discussion.
]]>Here is a fun quiz I prepared recently:
Hint: The meaning of C is completely different in all the questions.
Update: The quiz has been cracked in the comments section. The answers are provided below. If you want to think about the problem before you see the solutions, this is a good time to pause and think about it. There are spoilers ahead.
Here are the answers with a brief description about each answer:
Dennis : C :: Bjarne : C++
Dennis Ritchie created C and Bjarne Stroustrup created C++.
Sa : C :: Pa : G
The Sanskrit names of musical notes in an octave are Sa, Re, Ga, Ma, Pa, Dha, Ni, Sa. The equivalent names in many English-speaking countries are C, D, E, F, G, A, B, C.
6 : C :: 53 : I
Carbon has symbol C and atomic number 6. Iodine has symbol I and atomic number 53.
100 : C :: 500: D
In Roman numerals, 100 is represented by C and 500 by D.
50 : C :: 122 : F
50 degrees Farenheit equals 122 degrees Centigrade (50 °C = 122 °F).
Update: A similar quiz by Prasanna mentioned in the comments section has been cracked.
Here is Prasanna's quiz along with the answers:
195 : C :: 209 : J
In EBCDIC, the character 'C' has the code 195 and the character 'J' has the code 209.
Bohai : C :: Oolong : T
Bohai Sea and Oolong tea.
The long and the short of it is that I eventually decided - but this took many months - that the optimal structure would be a strict alternation between chapters and dialogues. Once that was clear, then I had the joyous task of trying to pinpoint the most crucial ideas that I wanted to get across to my readers and then somehow embodying them in both the form and the content of fanciful, often punning dialogues between Achilles and the Tortoise (plus a few new friends).
After the second chapter (Chapter II: Meaning and Form in Mathematics) there is a dialogue between Achilles and the Tortoise on telephone. The title of the dialogue is Sonata for Unaccompanied Achilles. The text shows the transcript of only one end of the call, only what Achilles speaks. The Tortoise is at the far end of the call. The sentences spoken by the Tortoise at the other end are not present in the text. This makes the reading experience very interesting as we keep guessing about what is going on at the other end.
It starts in this manner:
Achilles: Hello, this is Achilles.
Achilles: Oh, hello, Mr. T. How are you?
Achilles: A torticollis? Oh, I'm sorry to hear it. Do you have any idea what caused it?
As the dialogue proceeds, they share a few puzzles. Here is the first one from the Tortoise.
Achilles: A word with the letters 'A', 'D', 'A', 'C' consecutively inside it … Hmm … What about "abracadabra"?
Achilles: True, "ADAC" occurs backwards, not forwards, in that word.
Achilles: Hours and hours? It sounds like I'm in for a long puzzle, then. Where did you hear this infernal riddle?
Here is the second puzzle from Achilles:
Achilles: Say, I once heard a word puzzle a little bit like this one. Do you want to hear it? Or would it just drive you further into distraction?
Achilles: I agree - can't do any harm. here it is: What's a word that begins with the letters "HE" and also ends with "HE"?
Achilles: Very ingenious - but that's almost cheating. It's certainly not what I meant!
Achilles: Of course you're right - it fulfills the conditions, but it's a sort of "degenerate" solution. There's another solution which I had in mind.
Achilles: That's exactly it! How did you come up with it so fast?
Achilles: So here's a case where having a headache actually might have helped you, rather than hindering you. Excellent! But I'm still in the dark on your "ADAC" puzzle.
If you want to think about these puzzles, this is a good time to pause and think about them. There are spoilers ahead.
It didn't take much time for me to solve the puzzle because I cheated with the word list file available on Linux distributions. Here is what I found with my Debian 5.0 (lenny) system:
$ grep 'adac' /usr/share/dict/words headache headache's headaches $ grep '^he.*he$' /usr/share/dict/words headache heartache
The answers to both puzzles seem to be "HEADACHE". Take another look at the last sentence in the dialogue above. It makes sense now as Achilles says that having a headache might have helped the Tortoise.
Later in the dialogue the Tortoise offers figure and ground as hints to the "ADAC" puzzle.
Achilles: Well, normally I don't like hints, but all right. What's your hint?
Achilles: I don't know what you mean by "figure" and "ground" in this case.
Achilles: Certainly I know Mosaic II! I know ALL of Escher's works. After all, he's my favorite artist. In any case, I've got a print of Mosaic II hanging on my wall, in plain view from here.
Achilles: Yes, I see all the black animals.
Achilles: Yes, I also see how their "negative" space - what's left out - defines the white animals.
Achilles: So THAT's what you mean by "figure" and "ground". But what does that have to do with the "ADAC" puzzle?
Achilles: Oh, this is too tricky to me. I think I'M starting to get a headache.
The famous painting discussed in the dialogue can be found here: https://www.wikiart.org/en/m-c-escher/mosaic-ii. We can see how the black animals form the figure (the positive space) and how the background or ground (the negative space) beautifully fits all the white animals.
The figure and ground in hints make sense now. The first puzzle has "ADAC" in the question. Let us consider "ADAC" as the figure or the positive space. If we remove "ADAC" from "HEADACHE", we are left with the ground or negative space, which consists of "HE" and "HE" at the beginning and the end of the word. The figure is used in the first puzzle and the ground is used in the second puzzle
By the way, what is the first answer from the Tortoise that Achilles finds very ingenious but degenerate? I believe, it is "HE" as this word both begins and ends with "HE".
The funny thing about this dialogue is that both of them asked two puzzles to each other without knowing that the answers to them were the same. A similar thing happened to me when a colleague of mine and I challenged each other with combinatorics puzzles. See this blog post for the story: Combinatorics Coincidence.
]]>I joined RSA a few weeks ago. It is a small and delightful place with a great work culture. It was founded by Ron Rivest, Adi Shamir, and Leonard Adleman (the inventors of the RSA algorithm) in 1982. The company develops and sells the RSA BSAFE cryptography libraries, SecurID two-factor authentication tokens, and several network security software products these days.
I met several engineers here who are fond of mathematics in general and combinatorics in particular. The work often involves analysis of authentication tokens, key strengths, information entropy, etc. I think that such work naturally attracts engineers who have an affinity for combinatorics. As a result, discussions about combinatorics problems often occur at the cafeteria. Probability theory is another popular topic of discussion. Of course, often combinatorics and probability theory go hand in hand.
At the cafeteria one day, I joined in on a conversation about combinatorics problems. During the conversation, I happened to share the following problem:
For integers \( n \ge 1 \) and \( k \ge 1, \) \[ f_k(n) = \begin{cases} n & \text{if } k = 1, \\ \sum_{i=1}^n f_{k-1}(i) & \text{if } k \ge 2. \end{cases} \] Find a closed-form expression for \( f_k(n). \)
Soon after I shared the above problem, a colleague of mine shared this problem with me:
Consider the following pseudocode with k
nested loops:
x = 0
for c_{1} in 0 to (n - 1):
for c_{2} in 0 to (c_{1} - 1):
...
for c_{k} in 0 to (c_{k-1} - 1):
x = x + 1
What is the final value of x
after the outermost loop
terminates?
With one problem each, we went back to our desks. As I began solving the nested loops problem shared by my colleague, I realized that the solution to his problem led me to the recurrence relation in the problem I shared with him.
In the nested loops problem, if \( k = 1, \) the final value of \( x \) after the loop terminates is \( x = n. \) This is also the value of \( f_1(n). \)
If \( k = 2, \) the inner loop with counter \( c_2 \) runs once when \( c_1 = 0, \) twice when \( c_1 = 1, \) and so on. When the loop terminates, \( x = 1 + 2 + \dots + n. \) Note that this series is same as \( f_2(n) = f_1(1) + f_1(2) + \dots + f_1(n). \)
Extending this argument, we now see that for any \( k \ge 1, \) the final value of \( x \) is \[ f_k(n) = f_{k-1}(1) + f_{k-1}(2) + \dots + f_{k-1}(n). \] In other words, the solution to his nested loops problem is the solution to my recurrence relation problem. It was an interesting coincidence that the problems we shared with each other had the same solution.
The closed form expression for the recurrence relation is \[ f_k(n) = \binom{n + k - 1}{k}. \] It is quite easy to prove this using the principle of mathematical induction. Since we know that this is also the result of the nested loops problem, we can also arrive at this result by another way.
In the nested loops problem, the following inequalities are always met due to the loop conditions: \[ n - 1 \ge c_1 \ge c_2 \ge \dots \ge c_k \ge 0. \] The variables \( c_1, c_2, \dots, c_k \) take all possible arrangements of integer values that satisfy the above inequalities. If we find out how many such arrangements are there, we will know how many times the variable \( x \) is incremented.
Let us consider \( n - 1 \) similar balls and \( k \) similar sticks. For every possible permutation of these balls and sticks, if we count the number of balls to the right of the \( i \)th stick where \( 1 \le i \le k, \) we get a number that the variable \( c_i \) holds in some iteration of the \( i \)th loop. Therefore the variable \( c_i \) is represented as the number of balls lying on the right side of the \( i \)th stick.
The above argument holds good because the number of balls on the right side of the first stick does not exceed \( n - 1, \) the number of balls on the right side of the second stick does not exceed the number of balls on the right side of the first stick, and so on. Thus the inequalities mentioned earlier are always satisfied. Also, any set of valid values for \( c_1, c_2, \dots, c_k \) can be represented as an arrangement of these sticks and balls.
The number of permutations of \( n - 1 \) similar balls and \( k \) similar sticks is \[ \frac{(n + k - 1)!}{(n - 1)! \, k!} = \binom{n + k - 1}{k}. \] This closed-form expression is the solution to both the recurrence relation problem and the nested loops problem.
]]>Here is a classic quine I came across a few days ago in a mailing list:
main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}
This program is written in K&R C. The current version of GCC
compiles it fine. It is a valid quine on ASCII machines because this
program uses the integer code 34
to print the quotation
mark ("
) character. This will be explained further in
the next section. On another implementation of the C compiler which
does not use ASCII code for the quotation mark character, the
program needs to be modified to the use the correct code.
Here are some commands that demonstrate the quine:
$ printf '%s' 'main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}' > quine.c $ cc quine.c && ./a.out > out.txt && diff quine.c out.txt $ cat quine.c; echo main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);} $ ./a.out main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}
The source code of this quine does not end with a newline.
The -n
option of GNU echo ensures that the source code
file is created without a terminating newline.
Let us take a close look at how the quine introduced in the previous section works. Let us add some newlines in the source code of this quine for the sake of clarity.
main()
{
char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";
printf(s,34,s,34);
}
This is almost the same program presented in the previous section. Only a few newlines have been added to it to make the program easier to read.
We can see that the printf
call uses the
string s
as the format string. The format string
contains three conversion
specifications: %c
, %s
,
and %c
. The arguments for these conversions
are: 34
, the string s
itself,
and 34
once again. Note that 34
is the
ASCII code for the quotation mark character ("
). With
that in mind, let us now construct the output of
the printf
call in a step-by-step manner.
The initial portion of the output consists of the format string from the beginning up to, but not including, the first conversion specification copied unchanged to the output stream. Here it is:
main(){char*s=
Then the first conversion specification %c
is
processed, the corresponnding argument 34
is taken, and
a quotation mark is printed like this:
"
Then the second conversion specification %s
is
processed. The corresponding argument is the string s
itself, so the entire string is printed like this:
main(){char*s=%c%s%c;printf(s,34,s,34);}
Then the third conversion specification %c
is
processed. The corresponding argument is 34
again, so
once again a quotation mark is printed like this:
"
Finally, the rest of the format string is copied unchanged to produce the following output:
;printf(s,34,s,34);}
Here are all the five parts of the output presented next to each other:
main(){char*s=
"
main(){char*s=%c%s%c;printf(s,34,s,34);}
"
;printf(s,34,s,34);}
Writing them all out in a single line, we get this:
main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}
This output matches the source code of the program thus confirming that our program is a quine.
The source code of the classic quine presented above does not terminate with a newline. I found that a little bothersome because I am used to always terminating my source code with a single trailing newline at the end. So I decided to modify that quine a little to ensure that it always ends with a newline. This is the quine I arrived at:
main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34,10);}%c";printf(s,34,s,34,10);}
Compared to the quine in the previous sections, this one has an
additional %c
at the end of the formal string and the
integer 10
as the corresponding argument to ensure that
the output ends with a newline. Here is a demonstration of this
quine:
$ echo 'main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34,10);}%c";printf(s,34,s,34,10);}' > quine.c $ cc quine.c && ./a.out > out.txt && diff quine.c out.txt $ cat quine.c main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34,10);}%c";printf(s,34,s,34,10);} $ ./a.out main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34,10);}%c";printf(s,34,s,34,10);}
The classic C quines presented above are written in K&C. They do not conform to the C standard. However, with some modifications to the quines presented above, we can get a quine that conforms to the C89 standard:
#include <stdio.h>
int main(){char*s="#include <stdio.h>%cint main(){char*s=%c%s%c;printf(s,10,34,s,34,10);return 0;}%c";printf(s,10,34,s,34,10);return 0;}
Here is a demonstration of this quine:
$ echo '#include <stdio.h> int main(){char*s="#include <stdio.h>%cint main(){char*s=%c%s%c;printf(s,10,34,s,34,10);return 0;}%c";printf(s,10,34,s,34,10);return 0;}' > quine.c $ cc -std=c89 -Wall -Wextra -pedantic quine.c && ./a.out > out.txt && diff quine.c out.txt $ cat quine.c #include <stdio.h> int main(){char*s="#include <stdio.h>%cint main(){char*s=%c%s%c;printf(s,10,34,s,34,10);return 0;}%c";printf(s,10,34,s,34,10);return 0;} $ ./a.out #include <stdio.h> int main(){char*s="#include <stdio.h>%cint main(){char*s=%c%s%c;printf(s,10,34,s,34,10);return 0;}%c";printf(s,10,34,s,34,10);return 0;}]]>