Hexadecimal based FizzBuzz in Python

De openkb
Aller à : Navigation, rechercher

Sommaire

Questions

I m sure that for most of you should be familiar with what FizzBuzz is.

For those who don t know what I m stating here. Here is what FizzBuzz is:

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".

To most of you, this may be very easy to create.

Though after browsing online, I ve found some posts where they were challenged not to use the modulus operator.

I found this python code interesting:

m = [None, "Fizz", "Buzz", "FizzBuzz"]
v = 0x30490610
for i in range(1, 101):
    j = v & 3
    print(m[j] if j else i)
    v = v >> 2 | j << 28

The result:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz

My question is how does it do it?

I understand that the v variable contains a hexadecimal value.

How does that achieve creating FizzBuzz? How would you explain this to a total beginner?

Answers

First, we observe that the FizzBuzz pattern is cyclic with a length of 15, since n % 3 = (n + 15) % 3, and n % 5 = (n + 15) % 5.

Whether n is divisible by 3 and/or 5 can be stored with two bits of information: 00 for neither, 01 for divisble by 3, 10 for divisible by 5, and 11 for divisible by both 3 and 5.

The FizzBuzz "answers" for the numbers 1 to 15 are as follows, from right to left:

11 00 00 01 00 10 01 00 00 01 10 00 01 00 00.

Notice that every third bit pair has the right bit set, and every fifth bit pair has the left bit set. The rightmost pair corresponds to the number 1, and the leftmost pair corresponds to the number 15. It is perhaps more clear to separate out the left bits and the right bits:

v5:    1. 0. 0. 0. 0. 1. 0. 0. 0. 0. 1. 0. 0. 0. 0.
v3:    .1 .0 .0 .1 .0 .0 .1 .0 .0 .1 .0 .0 .1 .0 .0
v5|v3: 11 00 00 01 00 10 01 00 00 01 10 00 01 00 00

If we convert this string of bits to hexadecimal, we get the magic constant v from your snippet: 0x30490610.

We can extract the bottom two bits of v with the expression j = v & 3, since the number 3 has the bottom two bits set and the rest unset. (This is the "bitwise AND"-operator of Python.)

We can cycle around the 2*15 = 30 bits by shifting v two bits to the right v >> 2, and then adding the two bits in the other end, (v >> 2) | (j << 28). (These are the left shift and right shift operators of Python, which also work in a bitwise fashion.)

In this way, v can be seen as a "queue" containing 2-bit elements, each element corresponding to the "correct FizzBuzz answer" to one of the next 15 numbers to be processed. Once an element j is popped from this queue, it is pushed onto the other end, so it is ready again in 15 iterations from now.

One final thing: The syntax print(m[j] if j else i) means "If j is not a falsy value such as 0, then print m[j]; otherwise, print i." Since m[1], m[2] and m[3] contain the right strings corresponding to our 2-bit representation of FizzBuzz answers, and j is always in the range 0 to 3, the output is correct.

As an exercise, try to change v to 0x39999999 and see if you can explain the behavior. (Hint: 9 in hexadecimal is 10 01 in binary.)

  Update:    Here s a variant of the program. I have replaced the hexadecimal value v by an explicit queue q of responses, and the scary-looking v = v >> 2 | j << 28 has been replaced by a pop from front and push to back, q.append(q.pop(0)).
q = [  ,   ,  Fizz ,   ,  Buzz ,
      Fizz ,   ,   ,  Fizz ,  Buzz ,
       ,  Fizz ,   ,   ,  FizzBuzz ]
for i in range(1, 101):
    print(q[0] or i)
    q.append(q.pop(0))

We can also add a separate fizz and buzz queue:

f = [  ,   ,  Fizz ]
b = [  ,   ,   ,   ,  Buzz ]
for i in range(1, 101):
    print((f[0] + b[0]) or i)
    f.append(f.pop(0))
    b.append(b.pop(0))

Since is a falsy value, (f[0] + b[0]) or i will print the integer i whenever both f[0] and b[0] is the empty string.

Source

License : cc by-sa 3.0

http://stackoverflow.com/questions/33199156/hexadecimal-based-fizzbuzz-in-python

Related

Outils personnels
Espaces de noms

Variantes
Actions
Navigation
Outils