DEV Community

Nathan Kallman
Nathan Kallman

Posted on • Updated on • Originally published at kallmanation.com

Unconditional Challenge: FizzBuzz without `if`

As the title says, make a classic FizzBuzz function or method without using if/else (or equivalents like ternaries, ?:, sneaky).

Specifically:

  1. The function should accept one argument, assume it will always be a positive integer.
  2. The function should return a string (or something coercible into a string in loosely typed languages) according to the following rules:
    1. If the given number is divisible by 3, then return Fizz
    2. If the given number is divisible by 5, then return Buzz
    3. If the given number is divisible by both 3 and 5, then return the combination FizzBuzz
    4. If the given number is none of those things, then return the given number

The expected outputs for the first fifteen numbers in order is:

1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz
Enter fullscreen mode Exit fullscreen mode

Hard mode

Do this without "secret" conditionals like || and && (in loosely typed languages like JavaScript) or null coalescing operators like ?? or null safe operators like ?. or &..

Also no looping constructs that could be abused into a conditional like while or for.

Hint number 1

I tagged functional on this post because functional programming can be used to solve this.

Hint number 2

I tagged oop on this post because the original object oriented concepts of message passing can be used to solve this.


Post below with your answers!

Think this is impossible? I'll post my own answers in both FP and OOP styles next week.

(If you were hoping for the next installment of my With Only CSS series, styling radio buttons, that's coming on Monday, follow me so you won't miss it!)

Oldest comments (48)

Collapse
 
avaq profile image
Aldwin Vlasblom

Do you consider something like this cheating, because it builds on filter, a built-in, to handle the conditional logic?

const isFizz = n => n % 3 === 0
const isBuzz = n => n % 5 === 0
const complement = f => x => !(f (x))

const fizzbuzz = n => {
  const fizz = [n].filter(isFizz).map(_ => 'Fizz')
  const buzz = [n].filter(isBuzz).map(_ => 'Buzz')
  const num = [n].filter(complement(isFizz)).filter(complement(isBuzz)).map(String)
  return `${fizz.join('')}${buzz.join('')}${num.join('')}`
}
Collapse
 
kallmanation profile image
Nathan Kallman

Clever! I'll accept it for the normal challenge :)

If you want hard mode though, I think you'll have to show an implementation of .filter that doesn't use if, ?:, ||, &&, ?., or ??. (I'll allow .reduce to be used though, as well as .map)

Well done, thanks for commenting!

Collapse
 
avaq profile image
Aldwin Vlasblom • Edited

Okay, well, my next solution is probably not what you were expecting. It builds on the idea that a boolean can be used as a number. I think it meets the hardcore requirements.

const isFizz = n => n % 3 === 0
const isBuzz = n => n % 5 === 0
const isNeither = n => !isFizz(n) && !isBuzz(n)

// the magic:
const optional = (f, x) => Array(Number(f(x))).fill(x)

const fizzbuzz = n => {
  const fizz = optional(isFizz, n).map(_ => 'Fizz')
  const buzz = optional(isBuzz, n).map(_ => 'Buzz')
  const num = optional(isNeither, n).map(String)
  return `${fizz.join('')}${buzz.join('')}${num.join('')}`
}
Thread Thread
 
kallmanation profile image
Nathan Kallman

Very nice! I think you have met the hardcore requirements.

And I think slightly less code than my functional solution will be, well done!

Collapse
 
jpantunes profile image
JP Antunes

Does this one count for the hard mode?

const fizzBuzz = n => {
    const mapper = (arr, modulo, txt) => arr
                                    .filter(e => e % modulo == 0)
                                    .forEach(e => arr[arr.indexOf(e)] = txt);
    let x = 1;
    const range = [...Array(n)].map(_ => x++)

    mapper(range, 15, 'FizzBuzz');
    mapper(range, 5, 'Buzz');
    mapper(range, 3, 'Fizz');

    return range.toString();
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kallmanation profile image
Nathan Kallman

Nice solution! For hard mode, I think either do it without .filter or show how .filter could be implemented according to the rules.

Collapse
 
jpantunes profile image
JP Antunes

How about with for loops?

const fizzBuzz = n => {
    let x = 1;
    const range = [...Array(n)].map(_ => x++);
    for (let i = 2; i <= n; i += 3) range[i] = 'Fizz';
    for (let i = 4; i <= n; i += 5) range[i] = 'Buzz';
    for (let i = 14; i <= n; i += 15) range[i] = 'FizzBuzz';
    return range.toString();
}
Thread Thread
 
kallmanation profile image
Nathan Kallman

Nice! I'll accept it even though I said no for loops because I like that you used the stepping statement to simply go through the multiples of 3,5,15. That's creative!

I was more expecting someone to abuse the i <= n into the n % 3 type of check in a traditional fizz buzz. (especially abusing while(n % 3) into a more convoluted if)

Thread Thread
 
jpantunes profile image
JP Antunes

I missed that! Sorry, but I was running out of ideas :-) Looking forward to seeing your solution(s)!

Collapse
 
vidit1999 profile image
Vidit Sarkar

Thanks for the problem. I guess this will not satisfy your conditions, but here is my solution,

const fizzbuzz = n => (!(n%15) && "FizzBuzz") ||
                      (!(n%5) && "Buzz") ||
                      (!(n%3) && "Fizz") ||
                      n
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kallmanation profile image
Nathan Kallman

It's a nice answer! It satisfies the normal rules (no if or ?:)

Thanks for taking the time to submit a solution!

Collapse
 
vidit1999 profile image
Vidit Sarkar

Thanks for the reply. I think re-submission is also allowed. So, here is a C++ solution, which again, may not satisfy all your conditions.

string fizzbuzz(int n){
    string num_string = to_string(n);

    string all_options[5][3] = {
        {"FizzBuzz" ,"Buzz" , "Buzz"},
        {"Fizz" , num_string, num_string},
        {"Fizz" , num_string, num_string},
        {"Fizz" , num_string, num_string},
        {"Fizz" , num_string, num_string}
    };

    return all_options[n%5][n%3];
}
Thread Thread
 
kallmanation profile image
Nathan Kallman

Well done! I think you've got a solution

Collapse
 
vonheikemen profile image
Heiker • Edited

I'm sure I could make a fancy function composition but my brain is satisfied with this.

const Troo = (iff, elz) => iff;
const Falz = (iff, elz) => elz;

const If  = (boolz, iff, elz) => boolz(iff, elz);

const Boolz = {
  from_bool: bool => [Falz, Troo][Number(bool)],
};

const is_fizz = n => Boolz.from_bool(n % 3 === 0);
const is_buzz = n => Boolz.from_bool(n % 5 === 0);

const fizzbuzz = n => If(
  is_fizz(n),
  If(is_buzz(n), "FizzBuzz", "Fizz"),
  If(is_buzz(n), "Buzz", n)
);

const range = (end) => Array.from({ length: end }, (val, index) => index + 1);

console.log(
  range(15)
    .map(fizzbuzz)
    .join(", ")
);
Collapse
 
kallmanation profile image
Nathan Kallman

Awesome! I think you're really getting at the heart of the prompt by defining true and false as functions.

Collapse
 
bashunaimiroy profile image
Bashu Naimi-Roy

this blew me away, thank you for posting it

Collapse
 
vonheikemen profile image
Heiker

Glad you like it. I got the idea from this talk by Anjana Vakil.

Thread Thread
 
bashunaimiroy profile image
Bashu Naimi-Roy

Aha! She has several functional programming in JavaScript videos I've been putting off for a while, I think this is my cue to watch one. I'd like to better understand your solution. Cheers!

 
avaq profile image
Aldwin Vlasblom

You are referring to the following clause:

Do this without "secret" conditionals like || and && (in loosely typed languages like JavaScript)

My understanding is that this applies to the usage of such operators as conditionals when they are applied to non-boolean types. For example:

const fizz = n % 3 === 0 && 'Fizz' || String(n)

In my code, it is used strictly on Boolean values, which I don't think was against the rules.

Thread Thread
 
kallmanation profile image
Nathan Kallman

It uses it in a strictly boolean sense, which I'll allow. What isn't allowed is the loose/early-return way JS can use &&; a contrived example that would not pass hard mode:

const fizzbuzz = n => (n % 3 && (n % 5 && n || "Buzz")) || (n % 5 && "Fizz" || "FizzBuzz")

(an easy litmus test: does changing the order of the && expression change the result?)

Collapse
 
kallmanation profile image
Nathan Kallman

I think your .get is a secret conditional. (It will return the value at the given key or the default IF the value is undefined)

If you can show how to implement .get without an if then I think this is a great answer!

 
kallmanation profile image
Nathan Kallman

Excellent! I think you've checked off all the boxes. Thanks for submitting!

Collapse
 
jpantunes profile image
JP Antunes

Still not perfect, but this one comes with a special thanks to Mr. Kevlin Henney

const fizzBuzz = n => {
    const isFizzBuzz = n => ( {false: '', true: 'Fizz'}[n % 3 == 0] 
                            + {false: '', true: 'Buzz'}[n % 5 == 0] 
                            || n.toString() );
    let x = 1;
    return [...Array(n)].map(_ => isFizzBuzz(x++)).toString();                             
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kallmanation profile image
Nathan Kallman

Nice! I like how it uses false and true as keys on an object to select the resulting string.

If you can get rid of the || usage, then this will meet the hard mode requirements...

Collapse
 
jpantunes profile image
JP Antunes • Edited

Well Mr. Henney has a great answer:

const fizzBuzz = n => {
  const test = (d, s, x) => n % d == 0 ? _ => s + x('') : x;
  const fizz = x => test(3, 'Fizz', x);
  const buzz = x => test(5, 'Buzz', x);

  return fizz(buzz(x => x))(n.toString());
}

edit: but it still has a conditional...

Collapse
 
kallmanation profile image
Nathan Kallman

Happy Juneteenth! I love everyone's approaches here, it's fun to see the way each person thinks through the problem.

I'll be posting my answers on Monday (along with the next installment of With Only CSS) so encourage your friends to give this a shot over the weekend (or take another go yourself)!

Follow me and react with a 🔖 so you remember to come back and see what I post here.

Drop a 🦄 or ❤️ if you'd be interested in some follow up posts going more in depth into each (OO/FP) solution: how they work; how they are similar; and how they are different.

Thanks everyone who took the time to post a solution to my little challenge; I'll see you all on Monday!

Collapse
 
kallmanation profile image
Nathan Kallman • Edited

As promised, here is my functional approach to solving this (deeper article on it to come soon)

const functionalTrue = (onTrue, onFalse) => onTrue;
const functionalFalse = (onTrue, onFalse) => onFalse;
const isDivisible = (dividend, divisor) => [functionalTrue, ...Array(divisor).fill(functionalFalse)][dividend % divisor];

const functionalFizzBuzz = (n) => {
  const divisible_by_three = isDivisible(n, 3);
  const divisible_by_five = isDivisible(n, 5);
  return divisible_by_three(divisible_by_five("FizzBuzz", "Fizz"), divisible_by_five("Buzz", n));
};

Similar to Heiker's solution above; the main part of this solution is defining "true" and "false" as functions taking the same two parameters but returning one or the other.

Collapse
 
kallmanation profile image
Nathan Kallman

And here is my object-oriented approach (deeper article on it to come soon)

const baseBoolean = {
  setThen: function(then) { return { ...this, then }; },
  setOtherwise: function(otherwise) { return { ...this, otherwise }; },
};

const objectOrientedTrue = {
  ...baseBoolean,
  evaluate: function() { return this.then; },
};

const objectOrientedFalse = {
  ...baseBoolean,
  evaluate: function() { return this.otherwise; },
};

const objectOrientedNumber = {
  value: 0,
  isaMultipleCache: [objectOrientedFalse],
  setValue: function(n) { return { ...this, value: n, isaMultipleCache: [objectOrientedTrue, ...Array(n).fill(objectOrientedFalse)] }; },
  isaMultipleOf: function(dividend) { return this.isaMultipleCache[dividend.value % this.value]; }
};

const objectOrientedFizzBuzz = {
  for: function(n) {
    const number = objectOrientedNumber.setValue(n);
    return this.three
               .isaMultipleOf(number)
               .setThen(
                 this.five
                     .isaMultipleOf(number)
                     .setThen("FizzBuzz")
                     .setOtherwise("Fizz")
                     .evaluate()
               )
               .setOtherwise(
                 this.five
                     .isaMultipleOf(number)
                     .setThen("Buzz")
                     .setOtherwise(number.value)
                     .evaluate()
               )
               .evaluate();
  },
  three: objectOrientedNumber.setValue(3),
  five: objectOrientedNumber.setValue(5),
};

Similarly to the functional approach; the main part of this solution is defining "true" and "false" as objects. But now instead of parameters, the objects have the same interface and return one or the other of the attributes set on the object.

Collapse
 
kallmanation profile image
Nathan Kallman • Edited

You can see both solutions in action on this codepen (planning one final post doing a compare/contrast between the two approaches)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited
function map(compare, say, next = v => v) {
    return function(value) {
        return [() => say, next][Math.sign(compare(value))](value)
    }
}
const process = map(
    v => (v % 3) + (v % 5),
    "fizzbuzz",
    map(v => v % 5, "buzz", map(v => v % 3, "fizz"))
)

for (let i = 1; i < 31; i++) {
    console.log(process(i))
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Slightly shorter without bothering to call out for variable potential conditions and using anons:


let map = (compare, say, next = v => v) => v => [() => say, next][Math.sign(v % compare)](v)
const process = map(15, "FizzBuzz", map(5, "Buzz", map(3, "Fizz")))

Enter fullscreen mode Exit fullscreen mode
Collapse
 
kallmanation profile image
Nathan Kallman

Well done! Clever use of Math.sign to select either the first or second element from an array

Collapse
 
bashunaimiroy profile image
Bashu Naimi-Roy

I'm thinking of an absolutely egregious version using try and catch, but I'm going to go ahead and assume that try/catch would be a "surrogate conditional"

Collapse
 
kallmanation profile image
Nathan Kallman

😂 it probably is, but now I want to see it implemented with just try/catch!

Collapse
 
bashunaimiroy profile image
Bashu Naimi-Roy

holy COW did I find out a lot of things about object destructuring while making this. Thanks for the challenge, Nathan. It was so great to notice how instinctively I reach for a conditional.

Collapse
 
kallmanation profile image
Nathan Kallman

I'm glad you liked it! I think its good to do a little self reflection on the way we do things every once in a while.

And WOW is that an impressive use of destructuring in JS. I knew about each of those things independently; I never have thought about putting them all together like that!

Collapse
 
bashunaimiroy profile image
Bashu Naimi-Roy • Edited

I am posting for a friend who found some absolutely mind blowing ideas.

First a one-liner that extends my destructuring idea.

const fn = n => ({ ['2xx0x10xx01x0'[n % 15]]: result = n } = ['Fizz', 'Buzz', 'FizzBuzz'], result);
Enter fullscreen mode Exit fullscreen mode

secondly a solution built around a regex from hell/heaven

let fn = n => String(n).replace(/^(?=.*[05]$)(?:[0369]|[147](?:[0369]|[147][0369]*(?:[258]|[147][0369]*[147]))*(?:[258]|[147][0369]*[147])|[258](?:[258]|[258][0369]*(?:[147]|[258][0369]*[258]))*(?:[147]|[258][0369]*[258]))*$/, 'FizzBuzz').replace(/^(?:[0369]|[147](?:[0369]|[147][0369]*(?:[258]|[147][0369]*[147]))*(?:[258]|[147][0369]*[147])|[258](?:[258]|[258][0369]*(?:[147]|[258][0369]*[258]))*(?:[147]|[258][0369]*[258]))*$/, 'Fizz').replace(/^\d*[05]$/, 'Buzz');
Enter fullscreen mode Exit fullscreen mode
Collapse
 
indebanvdhamer profile image
Eric Nijman

This could probably get simplified (I've seen the one-liner of this one), but I still wanted to post mine

const fbMap = [
  () => 'FizzBuzz',
  String,
  String,
  () => 'Fizz',
  String,
  () => 'Buzz',
  () => 'Fizz',
  String,
  String,
  () => 'Fizz',
  () => 'Buzz',
  String,
  () => 'Fizz',
  String,
  String,
];
const fizzBuzz = x => fbMap[x % fbMap.length](x);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
alfrederson profile image
Alfrederson

How about this:

Collapse
 
kallmanation profile image
Nathan Kallman

Nice!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.