I must admit that I did not see your approach as a possible solution until I tried your SQL statement – the problem description does set itself up rather nicely for alphanumeric sorting. It should be possible to slightly alter your SQL statement to derive another solution using COALESCE rather than GREATEST.

]]>select greatest(to_char(level), replace(instr(level/3,'.'),0,'Fizz'), replace(instr(level/5,'.'),0,'Buzz'), replace(instr(level/15,'.'),0,'FizzBuzz') ) from dual connect by level<=100;]]>

WITH N AS (SELECT 1 N FROM ALL_OBJECTS WHERE ROWNUM<=10) SELECT DECODE( TRUNC(ROUND(ABS(COS(1.04719733*ROWNUM)),1))+ TRUNC(ROUND(ABS(COS(0.6283184*ROWNUM)),1))*10, 11,'FizzBuzz',1,'Fizz',10,'Buzz',ROWNUM) FIZZBUZZ, TO_CHAR(TO_DATE(ROWNUM,'J'),'Jsp') FIZZBUZZ2 FROM N N1, N N2; FIZZBUZZ FIZZBUZZ2 -------- --------- 1 One 2 Two Fizz Three 4 Four Buzz Five Fizz Six 7 Seven 8 Eight Fizz Nine Buzz Ten 11 Eleven Fizz Twelve 13 Thirteen 14 Fourteen FizzBuzz Fifteen ... Buzz Ninety-Five Fizz Ninety-Six 97 Ninety-Seven 98 Ninety-Eight Fizz Ninety-Nine Buzz One Hundred]]>

select actual , case when mod(actual,15) = 0 then 'FizzBuzz' when mod(actual,3) = 0 then 'Fizz' when mod(actual,5) = 0 then 'Buzz' else nvl2(say_thousands,say_thousands||' ',null) || nvl2(say_hundreds,say_hundreds||' ',null) || filler || say_tens end in_words from ( select actual , case when actual_length = 4 then thousand_desc||' thousand' end say_thousands , case when hundred_desc is not null then hundred_desc||' hundred' end say_hundreds , case when actual_length >= 3 and last_2_digits > 0 then 'and ' end filler , case when last_2_digits between 1 and 15 then actual_desc when last_2_digits between 16 and 19 then unit_desc||ten_desc when last_2_digits between 20 and 99 then ten_desc||' '||unit_desc end say_tens from ( with bakers as (select 1 num,'one' dsc from dual union all select 2,'two' from dual union all select 3,'three' from dual union all select 4,'four' from dual union all select 5,'five' from dual union all select 6,'six' from dual union all select 7,'seven' from dual union all select 8,'eight' from dual union all select 9,'nine' from dual union all select 10,'ten' from dual union all select 11,'eleven' from dual union all select 12,'twelve' from dual union all select 13,'thirteen' from dual union all select 14,'fourteen' from dual union all select 15,'fifteen' from dual) , tens as (select 1 num,'teen' dsc from dual union all select 2,'twenty' from dual union all select 3,'thirty' from dual union all select 4,'fourty' from dual union all select 5,'fifty' from dual union all select 6,'sixty' from dual union all select 7,'seventy' from dual union all select 8,'eighty' from dual union all select 9,'ninety' from dual) , number_stack as (select to_char(rownum,'0999') string_number , rownum actual from dual connect by level <= 9999) select stack.actual , stack.actual_string , stack.last_2_digits , bakers1.dsc actual_desc , stack.actual_length , bakers4.dsc thousand_desc , bakers3.dsc hundred_desc , stack.hundreds , stack.tens , tens.dsc ten_desc , stack.units , bakers2.dsc unit_desc from ( select substr(string_number,length(string_number)-3,1) thousands , substr(string_number,length(string_number)-2,1) hundreds , substr(string_number,length(string_number)-1,1) tens , substr(string_number,length(string_number),1) units , actual , ltrim(string_number,' 0') actual_string , to_number(ltrim(substr(string_number,length(string_number)-1,2),' 0')) last_2_digits , length(ltrim(string_number,' 0')) actual_length from number_stack ) stack , bakers bakers1 , bakers bakers2 , bakers bakers3 , bakers bakers4 , tens where bakers1.num (+) = stack.last_2_digits and bakers2.num (+) = stack.units and bakers3.num (+) = stack.hundreds and bakers4.num (+) = stack.thousands and tens.num (+) = stack.tens and stack.actual between 1 and 100 ) ) order by actual /

ACTUAL IN_WORDS ---------- ----------------- 1 one 2 two 3 Fizz 4 four 5 Buzz 6 Fizz 7 seven 8 eight 9 Fizz 10 Buzz 11 eleven ... 96 Fizz 97 ninety seven 98 ninety eight 99 Fizz 100 Buzz]]>

Two programmers walk into two different bars, the mathematician walks between the bars.

What? Here is the original specification:

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”.

Think about that specification – is there sufficient information to produce a “correct” solution? In one of my comments in this article I showed a solution that uses recursion to display the integers from 1 to 100 in reverse numerical order, with the “Fizz” and “Buzz” keywords placed as requested. Did this solution meet the requirements? Maybe? Or should a double quote (**“**) have appeared at each end of the keywords? Should the numbers have appeared in numerical order? Should I have restricted the numbers output to ** just** the integers between 1 and 100 (inclusive)? We cannot safely assume that only the integer values should be considered – maybe the whole point of the exercise is to indentify those developers that will produce a solution without fully understanding the desired outcome (I did not see this perspective until it was 30 minutes too late).

A mathematician reading “Write a program that prints the numbers from 1 to 100” might produce a very different solution than a typical programmer. While there are a finite number of integers (whole numbers) between 1 and 100, there are an infinite number of numbers between 1 and 2, between 2 and 3, between 3 and 4, etc. OK, but computers store numbers in a certain precision that eliminates the possibility of an infinite number of number positions to the right of the decimal, so it might be helpful to pick a datatype for the solution. For fun, let’s pick the 32 bit datatype BINARY_FLOAT (4 data bytes plus one length byte). The number 1/3, written in decimal form, has an infinite number of “3” digits to the right of the decimal point. Let’s see how many digits a 32 bit BINARY_FLOAT supports when holding the value of 1/3:

SET SERVEROUTPUT ON DECLARE NUM BINARY_FLOAT; BEGIN NUM:=1/3; DBMS_OUTPUT.PUT_LINE(NUM); END; / 3.33333343E-001 PL/SQL procedure successfully completed.

It appears that roughly 9 digits to the right of the decimal are supported. Where did that “4” digit come from? Let’s check the documentation:

http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/datatype.htm#i16209

BINARY_FLOAT and BINARY_DOUBLE are approximate numeric datatypes. They store approximate representations of decimal values, rather than exact representations. For example, the value 0.1 cannot be exactly represented by either BINARY_DOUBLE or BINARY_FLOAT. They are frequently used for scientific computations. Their behavior is similar to the datatypes FLOAT and DOUBLE in Java and XMLSchema.

OK, so we must be a little careful when using that datatype (if it is good enough for science, no problem?). Let’s try an experiment – we will retrieve all of the numbers with 2 decimal places of accuracy between 1 and 100, and produce the requested output:

DECLARE NUM1 BINARY_FLOAT; NUM2 BINARY_FLOAT; I BINARY_FLOAT; FIZZBUZZ VARCHAR2(8); BEGIN FOR NUM1 IN 1..99 LOOP FOR I IN 0..99 LOOP NUM2:=NUM1 + 0.01 * I; IF I = 0 THEN IF NUM2/15 = TRUNC(NUM2/15) THEN FIZZBUZZ:='FizzBuzz'; ELSE IF NUM2/3 = TRUNC(NUM2/3) THEN FIZZBUZZ:='Fizz'; ELSE IF NUM2/5 = TRUNC(NUM2/5) THEN FIZZBUZZ:='Buzz'; ELSE FIZZBUZZ:=NULL; END IF; END IF; END IF; ELSE FIZZBUZZ:=NULL; END IF; DBMS_OUTPUT.PUT_LINE(COALESCE(FIZZBUZZ,TO_CHAR(NUM2,'990.0000000'))); END LOOP; END LOOP; NUM2:=100; IF NUM2/15 = TRUNC(NUM2/15) THEN FIZZBUZZ:='FizzBuzz'; ELSE IF NUM2/3 = TRUNC(NUM2/3) THEN FIZZBUZZ:='Fizz'; ELSE IF NUM2/5 = TRUNC(NUM2/5) THEN FIZZBUZZ:='Buzz'; ELSE FIZZBUZZ:=NULL; END IF; END IF; END IF; DBMS_OUTPUT.PUT_LINE(COALESCE(FIZZBUZZ,TO_CHAR(NUM2,'990.0000000'))); END; / 99.9000015 99.9100037 99.9199982 99.9300003 99.9400024 99.9499969 99.9599991 99.9700012 99.9800034 99.9899979 Buzz

Nice, although we have a couple seemingly random accuracy problems beyond the first digit to the right of the decimal point (but its good enough for science, so says the documentation). You can possibly see why I used nested FOR loops – I wanted to make certain that the numbers that are supposed to be integers actually return as integer values.

Now, extending the previous example, we will retrieve all of the numbers with 7 decimal places of accuracy between 1 and 100, and produce the output that was requested at the start of this blog article:

DECLARE NUM1 BINARY_FLOAT; NUM2 BINARY_FLOAT; I BINARY_FLOAT; FIZZBUZZ VARCHAR2(8); BEGIN FOR NUM1 IN 1..99 LOOP FOR I IN 0..9999999 LOOP NUM2:=NUM1 + 0.0000001 * I; IF I = 0 THEN IF NUM2/15 = TRUNC(NUM2/15) THEN FIZZBUZZ:='FizzBuzz'; ELSE IF NUM2/3 = TRUNC(NUM2/3) THEN FIZZBUZZ:='Fizz'; ELSE IF NUM2/5 = TRUNC(NUM2/5) THEN FIZZBUZZ:='Buzz'; ELSE FIZZBUZZ:=NULL; END IF; END IF; END IF; ELSE FIZZBUZZ:=NULL; END IF; DBMS_OUTPUT.PUT_LINE(COALESCE(FIZZBUZZ,TO_CHAR(NUM2,'990.0000000'))); END LOOP; END LOOP; NUM2:=100; IF NUM2/15 = TRUNC(NUM2/15) THEN FIZZBUZZ:='FizzBuzz'; ELSE IF NUM2/3 = TRUNC(NUM2/3) THEN FIZZBUZZ:='Fizz'; ELSE IF NUM2/5 = TRUNC(NUM2/5) THEN FIZZBUZZ:='Buzz'; ELSE FIZZBUZZ:=NULL; END IF; END IF; END IF; DBMS_OUTPUT.PUT_LINE(COALESCE(FIZZBUZZ,TO_CHAR(NUM2,'990.0000000'))); END; /

Did the output of the above program finish in 10 minutes or less? If Yes, then try the BINARY_DOUBLE datatype and head out to 15 places to the right of the decimal.

Repeating, I do not think that the problem was that 199 of 200 programmers could not solve the problem – I think that the problem was that 199 (or maybe just 150) of the programmers solved a different problem from what was actually requested. 🙂

]]>You made me work on my vacation. 😉

Below are three versions from my side (although the CASE is already mentioned in one of the comments above).

— simple CASE

select level, case when mod(level, 3) + mod(level, 5) = 0 then 'FizzBuzz' when mod(level, 3) = 0 then 'Fizz' when mod(level, 5) = 0 then 'Buzz' else to_char(level) end txt from dual connect by level <= 100;

–Cascaded DECODE

select level, decode(mod(level, 3) + mod(level, 5), 0, 'FizzBuzz', decode(mod(level, 3), 0, 'Fizz', decode(mod(level, 5), 0, 'Buzz', to_char(level)))) txt from dual connect by level <= 100;

— simple DECODE

select level, Nvl(decode(mod(level, 3), 0, 'Fizz') || decode(mod(level, 5), 0, 'Buzz'), to_char(level)) txt from dual connect by level <= 100;]]>

The solution that just selects the answers you want, and sorts the result into sequential order technique:

SELECT FIZZBUZZ FROM (WITH N AS ( SELECT LEVEL N FROM DUAL CONNECT BY LEVEL<=100) SELECT N.N, TO_CHAR(N.N) FIZZBUZZ FROM N WHERE MOD(N,3)>0 AND MOD(N,5)>0 UNION ALL SELECT N.N, 'Fizz' FROM N WHERE MOD(N,3)=0 AND MOD(N,5)>0 UNION ALL SELECT N.N, 'Buzz' FROM N WHERE MOD(N,3)>0 AND MOD(N,5)=0 UNION ALL SELECT N.N, 'FizzBuzz' FROM N WHERE MOD(N,3)=0 AND MOD(N,5)=0) ORDER BY N;

The “I shall confuse you with WITH blocks that reference each other and then left outer join the result to output what I want you to see”:

WITH V1 AS (SELECT ROWNUM N, TO_CHAR(ROWNUM) T FROM DUAL CONNECT BY LEVEL<=100), V2 AS (SELECT ROWNUM*12 N, 'FizzBuzz' T FROM DUAL CONNECT BY LEVEL<=6), V3 AS (SELECT * FROM (SELECT ROWNUM*3 N, 'Fizz' T FROM DUAL CONNECT BY LEVEL<=33) V3 WHERE V3.N NOT IN (SELECT N FROM V2)), V4 AS( SELECT * FROM (SELECT ROWNUM*5 N, 'Buzz' T FROM DUAL CONNECT BY LEVEL<=20) V4 WHERE V4.N NOT IN (SELECT N FROM V2)) SELECT COALESCE(V2.T,V3.T,V4.T,V1.T) FIZZBUZZ FROM V1, V2, V3, V4 WHERE V1.N=V2.N(+) AND V1.N=V3.N(+) AND V1.N=V4.N(+) ORDER BY V1.N;]]>

For some reason, I really like that solution. 🙂

]]>Three very nice examples! I will have to spend some time trying to determine how the first 2 of those examples work. It appears that the third example counts to 101, so maybe we should swap the 100 for 99 in that example.

]]>