0
\$\begingroup\$

Code first, elaboration later:

// Fizzbuzz: 1..100, %3->fizz, %5->buzz, %15->fizzbuzz
// noif: do not use if/switch statements or ternary expressions
// LIMIT must be a power of 10
// WIDTH must be equal to log10(LIMIT)

#include <stdio.h>
#include <string.h>

#define LIMIT 100
#define WIDTH 2

char *
itoa(int n)
{
    static char out[WIDTH + 1];

    for(char *outp = out + WIDTH - 1; outp >= out; --outp, n /= 10)
        *outp = '0' + n % 10;

    return out;
}

void
fizzbuzz(int n)
{
    static char *FBSTR[4] = {"", "Fizz", "Buzz"};

    FBSTR[3] = itoa(n);
    fputs(FBSTR[1 * (n%3 == 0)], stdout);
    fputs(FBSTR[2 * (n%5 == 0)], stdout);
    fputs(FBSTR[3 * (n%3 > 0 && n%5 > 0)], stdout);
    putchar('\n');
}

int
main(void)
{
    for(int i = 1; i <= LIMIT; ++i)
        fizzbuzz(i);

    return 0;
}

I've been reading a little bit about the anti-if campaign, and decided to try my hand at a challenge: Fizzbuzz but without if statements.

Because I'm not trying to be cheap, that also means no switch statements,

switch(n % 3){
    case 0: fputs("fizz", stdout); break;
}

no ternary expressions,

fputs((n % 3 == 0) ? ("fizz") : (""), stdout);

and no contrived while loops.

int lock = 0;

while(lock++ == 0 && n % 3 == 0)
    fputs("fizz", stdout);
\$\endgroup\$
1
\$\begingroup\$
char *
itoa(int n)
{
    static char out[WIDTH + 1];

    for(char *outp = out + WIDTH - 1; outp >= out; --outp, n /= 10)
        *outp = '0' + n % 10;

Nice bounds-check (outp >= out). This ensures that you don't overrun your buffer when given a very large integer. However, since out-1 is not a valid pointer value, outp >= out is an incorrect bounds-check for the same reason that

int decr(int x) {
    int y = x - 1;
    if (y > x) return 0;  // oops, overflow happened
    return y;
}

is an incorrect overflow-check.

Your bounds-check code also has another similarity to the above overflow-check code: When overflow happens, you return an in-range value that the caller might reasonably confuse for a valid result of the function. When itoa(x) returns "42", does it mean that x was 42, or does it mean that x was 1042 and overflow happened? It would be much better to say, if overflow happens, we return NULL.

Of course you'll have to express that "if" without if. ;)


itoa returns char*, but you don't expect the caller to modify the pointed-to chars, so you should declare it as returning a pointer to non-modifiable chars: const char*. Const is a contract.

Similarly, static char *FBSTR[4] should be static const char *FBSTR[4].


static const char *FBSTR[4] = {"", "Fizz", "Buzz", NULL};

would be much clearer to the human reader; it shows that you didn't miscount the number of array elements. In fact, you might even write

static const char *FBSTR[] = {"", "Fizz", "Buzz", NULL};

although the downside of that is that the number of indices is actually really important to your algorithm, so I can see a valid argument for keeping the 4 in this case.


fputs(FBSTR[1 * (n%3 == 0)], stdout);
fputs(FBSTR[2 * (n%5 == 0)], stdout);
fputs(FBSTR[3 * (n%3 > 0 && n%5 > 0)], stdout);
putchar('\n');

I did puzzle this out, but printing all those empty strings feels like a strange way to go about it. Why not simply

int idx = 1*(n%3 == 0)
        + 2*(n%5 == 0)
        + 3*(n%3 != 0 && n%5 != 0);
fputs(FBSTR[idx], stdout);
putchar('\n');

In which case you can combine the fputs and putchar into

puts(FBSTR[idx]);

If you think it would be easier to understand, you could even rewrite the index computation using bitwise operations:

const char *FBSTR[] = {itoa(n), "Fizz", "Buzz", "FizzBuzz"};
int idx = (n%3 == 0) + 2*(n%5 == 0);
puts(FBSTR[idx]);

To avoid reinventing the wheel, you could replace itoa(n) with snprintf(somelocalbuffer, sizeof somelocalbuffer, "%d", n). However, for this toy example, itoa does have a more ergonomic interface; and reinventing the wheel isn't necessarily a bad thing.

\$\endgroup\$
  • \$\begingroup\$ Lots of good advice, some I'd even thought about myself before posting (ambiguous itoa 42/...42, FBSTR should explicitly declare its fourth element, use of char const* instead of char*, ptr - ... - 1 could backfire if ... ever happens to be 0). I haven't been to CR in a while--should I edit my question to integrate your advice, or is that against the spirit of CR? \$\endgroup\$ – Braden Best Feb 22 at 1:04
  • \$\begingroup\$ I will say that I'm a bit confused by the third-to-last piece of advice, which seems to be telling me to add the three indices together, which, to my understanding, would cause my program to output itoa's buffer rather than "FizzBuzz" when n % 15 == 0 is true. \$\endgroup\$ – Braden Best Feb 22 at 1:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.