HNNewShowAskJobs
Built with Tanstack Start
Fizz Buzz without conditionals or booleans(evanhahn.com)
58 points by ingve 9 days ago | 71 comments
  • susam2 days ago

    Late to the party but here's a solution I worked out using roots of unity and cosines:

      from math import cos, pi
      for n in range(1, 101):
          print([n, 'Fizz', 'Buzz', 'FizzBuzz'][round((1 + 2 * cos(2 * pi * n / 3)) / 3 + 2 * (1 + 2 * cos(2 * pi * n / 5) + 2 * cos(4 * pi * n / 5)) / 5)])
    • nik90002 days ago |parent

      As it should be.

  • ramenprofitable3 days ago

    The suckless approach

      #include <stdio.h>
      #include <stdint.h>
      #include <stdlib.h>
    
      void fizzbuzz(int i) {
          uint32_t r3 = i % 3;
          uint32_t r5 = i % 5;
          uint32_t is_nonzero_3 = (r3 | -r3) >> 31;
          uint32_t is_nonzero_5 = (r5 | -r5) >> 31;
          uint32_t is_zero_3 = is_nonzero_3 ^ 1;
          uint32_t is_zero_5 = is_nonzero_5 ^ 1;
          uint32_t idx = (is_zero_5 << 1) | is_zero_3;
    
          const char *fmt[] = {
              "%d\n",
              "Fizz\n",
              "Buzz\n",
              "FizzBuzz\n"
          };
    
          printf(fmt[idx], i);
      }
    
      typedef void (*func_t)(int);
    
      void run_loop(int i);
      void stop_loop(int i) { exit(0); }
    
      func_t actions[] = { stop_loop, run_loop };
    
      void run_loop(int i) {
          fizzbuzz(i);
          int d = 99 - i;
          uint32_t is_neg = ((uint32_t)d) >> 31;
      }
    
      func_t actions_table[] = { run_loop, stop_loop };
    
      void run_loop_wrapper(int i) {
          fizzbuzz(i);
          int d = 99 - i;
          uint32_t is_neg = ((uint32_t)d) >> 31;
          actions_table[is_neg](i + 1);
      }
    
      int main() {
          actions_table[0] = run_loop_wrapper;
          run_loop_wrapper(1);
          return 0;
      }
  • userbinator3 days ago

    challenged listeners to “write Fizz Buzz with no booleans, no conditionals, no pattern matching, or other things that are like disguised booleans.”

    Without any other constraints, this is not an interesting challenge.

        print("<precalculated output goes here>")
    • moritzwarhier2 days ago |parent

        prompt("please enter the expected output of a solution to the fizz buzz problem, without any further enclosing text or explanations; especially: do not enter program code")
      
      should work in a browser JS environment (with layer 8) and with modern AI
  • brudgers3 days ago

    Obviously FizzBuzz is a property of integers

      Integer extend [
        fizzbuzz [ 
            (self \\ 15 = 0)
            ifTrue: ['fizzbuzz' printNl]
            ifFalse: [
                 (self \\ 3 = 0)
                 ifTrue: ['fizz' printNl]
                 ifFalse: [
                      (self \\ 5 = 0)
                      ifTrue: ['buzz' printNl]
                      ifFalse: [self printNl]
                 ]
            ]
        ]
      ]
    
      1 to: 100 by: 1 do: [:i | i fizzbuzz]
    • kqr3 days ago |parent

      How is this without conditionals?

      • carlmr2 days ago |parent

        Bin ifTrue

  • kiratp3 days ago

    A for loop has a conditional in it.

    Unless by conditionals we mean “no if/else” and not “no branch instructions”.

    • WhyNotHugo3 days ago |parent

      The conditional here only makes it stop when it reaches 100. The solution can be adapted to use a while loop if you’re okay with it running indefinitely.

      • kiratp3 days ago |parent

        A loop either never halts or has a conditional. I guess a compiler could elide a “while True:” to a branch-less jump instruction.

        One hack would be to use recursion and let stack exhaustion stop you.

        • nodja3 days ago |parent

          Count down i from 100 to 0 and do 1/i at the end of the loop :)

        • buzer3 days ago |parent

          Other would be to use goto (though Python doesn't have it) & introduce something that will panic/throw exception, like creating variable with value 1/(max-i).

          • pinusc2 days ago |parent

            Recursion would work for that, you don't need goto.

            The division by zero trick is pretty good too!

        • 2 days ago |parent
          [deleted]
        • TZubiri2 days ago |parent

          You could allocate 100 bytes and get a segfault on 101

  • jswelker2 days ago

    3 lines of python, no imports.

      fizzbuzz = [None, None, "Fizz", None, "Buzz", "Fizz", None, None, "Fizz", "Buzz", None, "Fizz", None, None, "FizzBuzz"]
    
      for i in range(1,100):
        print(fizzbuzz[i % 15] or i)
    
    Edit: I see I was a few hours late, and someone posted nearly the exact same solution. :(
    • genewitch2 days ago |parent

      does not "or" imply a boolean

      • jswelker2 days ago |parent

        It does.Will you accept max?

          fizzbuzz = ["", "", "Fizz", "", "Buzz", "Fizz", "", "", "Fizz", "Buzz", "", "Fizz", "", "", "FizzBuzz"]
        
          for i in range(1,101):
            print(max((fizzbuzz[(i-1)% 15],str(i),)))
        
        Probably still bools under the hood.
        • TZubiri2 days ago |parent

          Doesn't a for loop contain a conditional? (If i <101)

          • jswelker2 days ago |parent

            That is in arbitrary line I am willing to draw in the battle against pedantry. Surely though a fizzbuzz isn't supposed to run forever though right? Gotta end it somehow, and the for loop is idiomatic.

            • TZubiri2 days ago |parent

              It's a challenge and you can do whatever version you want.

              If I'm pedantring about it it's because it can be done with a stricter restriction (or at least I think it can be done).

              I'd also add that it isn't a particularly hard problem, so I would recommend that if you are having trouble with solving for the hardest interpretation of the problem, that you keep on trying because there is quite a simple solution.

              If I were hiring based on this, your answer is a pass for sure. But it isn't exactly clear to me that you know about cpu comparisions or conditional jumps. In fact I think if you knew about them you wouldn't call it an arbitrary line.

              I think there is a python solution as a prototype, but of course the robust solution would be in python to ensure control over the cpu instructions and avoid conditionals for good.

              • jswelker2 days ago |parent

                You got me. I'd surely fail your interview. I admit I know nothing about CPU comparisons and conditional jumps.

  • andriamanitra3 days ago

    I gave it a go in C (I wanted to do assembly but couldn't be arsed to write string to int and int to string conversions). [1] The trickiest part was figuring out how to terminate the program. My first attempt invoked nasal demons through dividing by zero, but I then realized I could intentionally cause a segfault with high probability (which is much better, right?). One could argue that my `fizz` and `buzz` variables are still "disguised booleans", but at least the generated assembly contains no branching or cmov instructions (aside from the ones inside libc functions like atoi and sprintf).

    [1] https://gist.github.com/Andriamanitra/5c20f367dc4570dd5c8068...

  • HWR_143 days ago

    What's a "disguised Boolean" in this context?

    • 3 days ago |parent
      [deleted]
    • nucleogenesis3 days ago |parent

      I think it was more about doing it without a Boolean-based branch construct like a ternary or switch or whatever flavor of thing that abstracts away the explicit checks for true/false by other means. Idk though for sure

    • kqr3 days ago |parent

      Something like this: https://entropicthoughts.com/fizzbuzz-through-monoids

    • gridspy3 days ago |parent

      I'd imagine that as any variable that only has two states.

  • 8 days ago
    [deleted]
  • pbiggar2 days ago

    Simple solution using modulo arithmetic and arrays. Relies on python shorthands that hides implicit branches for the number case though.

        def main():
          fizz_array = ["Fizz", "", ""]
          buzz_array = ["Buzz", "", "", "", ""]
    
          for n in range(1, 101):
            # Use modulo to index into arrays
            f = fizz_array[n % 3]
            b = buzz_array[n % 5]
    
            # Combine fizz and buzz
            result = f + b
        
            output = result + str(n)[len(result):]
        
            print(output)
        
        main()
    
    
    I couldn't figure out this line and had to rely on AI for it.

        output = result + str(n)[len(result):]
  • brudgers7 days ago

    No matter how you calculate FizzBuzz, it is bad engineering.

      const Fizzbuzz = "1 2. Fizz, 4 ... "
      Print Fizzbuzz
    • hyperhello3 days ago |parent

      What’s the point of the game without booleans?

  • unsnap_biceps8 days ago

    Much like stop50's solution, I also used the modulo, but I make use of the terminal to overwrite the number. It's only three lines of code, but I split up the list to be more readable on here.

    This works from 1 to 100000000000000000000 before it overflows, and 100000000000000000000 is above the max size of a unsigned 64 bit int, so I feel that it's good enough

        fizzbuzz = [
            "fizzbuzz            ",
            "", "",
            "fizz                ",
            "",
            "buzz                ",
            "fizz                ",
            "", "",
            "fizz                ",
            "buzz                ",
            "",
            "fizz                ",
            "", "" ]
        for n in range(99999999999999999999-30, 100000000000000000000):
            print(f"{n}\r{fizzbuzz[n%15]}")
    • kiratp3 days ago |parent

      A for loop has an implicit conditional in its stop condition check.

      • kstrauser3 days ago |parent

        I could see that both ways. Python’s for loops are different than, say, C’s, in that they always consume an iterator. The implementation is that it calls next(iter) until it raises a StopIteration exception, but you could argue that’s just an implementation detail and not cheating.

        If you wanted to be more general, you could use map() to apply the function to every member of the iterator, and implementation details aside, that feels solidly in the spirit of the challenge.

        • hug3 days ago |parent

          The dirty solution I wrote in Powershell does something similar:

          1..100 | % {"$_ $(('fizz','')[$_%3])$(('buzz','')[$_%5])"}

          I am not sure that using [$_%3] to index into a two-value array doesn't count as a "disguised boolean" thought.

    • floxy3 days ago |parent

      Very nice.

  • sovnwnt3 days ago

    > No disguised booleans

    Do indexes count? If not, there's a simple one liner

        def fizzbuzz(n):
            return [str(n), "fizz", "buzz", "fizzbuzz"][1-min(1, n%3) + (1-min(1,n%5))*2]
  • dullcrisp3 days ago

    vim +'exec "norm 99o"|%s/$/\=line(".")/|vert new|exec "norm i\r\rFizz\r\rBuzz\rFizz\r\r\rFizz\rBuzz\r\rFizz\r\r\rFizzBuzz"|exec "norm gg\<c-v>G$y"|bd!|let @q="10a \<esc>\"0gpj"|exec "norm gg10@q"|silent /100/+,$d|silent %s/\d\+\s\+\(\w\+\)/\1'

    Now I see it's the same solution as in the post.

    Edit: Actually all you need is vim -es +'exec "norm! i\r\rFizz\r\rBuzz\rFizz\r\r\rFizz\rBuzz\r\rFizz\r\r\rFizzBuzz\<esc>Vggy7P101GdG"|%s/^$/\=line(".")/|%p|q!'

  • MathMonkeyMan3 days ago

    Here's my attempt:

        # Multi-pass FizzBuzz
        n = 100 
        # [['1'], ['2'], ['3'], ['4'], ['5'], ...]
        seq = [[str(i)] for i in range(1, n + 1)] 
        # [['1'], ['2'], ['3', 'Fizz'], ['4'], ['5'], ...]
        for i in range(3, n +  1, 3): 
            seq[i-1].append('Fizz')
        # [['1'], ['2'], ['3', 'Fizz'], ['4'], ['5', 'Buzz'], ..., ['15', ''Fizz', 'Buzz'], ...]
        for i in range(5, n + 1, 5): 
            seq[i-1].append('Buzz')
        # Arithmetic equivalent to:
        # len=1 -> the whole thing (from zero to end, because zero = -zero)
        # len=2 -> the length-1 suffix (just Fizz or Buzz)
        # len=3 -> the length-2 suffix (Fizz and Buzz)
        # The branch is hidden in the slice syntax:
        # Python has to check whether `x` is negative in `terms[x:]`. 
        for terms in seq:
            print(''.join(terms[-(len(terms) - 1):]))
    
    Here's a version that uses generators instead of multiple passes over a list:

        # Single-pass FizzBuzz
        n = 100
    
        def numbers():
            for i in range(1, n+1):
                yield [str(i)]
    
        def fizzies():
            nums = numbers()
            try:
                while True:
                    yield next(nums)
                    yield next(nums)
                    yield [*next(nums), 'Fizz']
            except StopIteration:
                pass
    
        def buzzies():
            fzs = fizzies()
            try:
                while True:
                    yield next(fzs)
                    yield next(fzs)
                    yield next(fzs)
                    yield next(fzs)
                    yield [*next(fzs), 'Buzz']
            except StopIteration:
                pass
    
        for terms in buzzies():
            print(''.join(terms[-(len(terms) - 1):]))
    
    Edit: Can't resist -- unbounded without loops, but recursion blows the call stack (granted, well after 100):

        def numbers(i=1):
            yield [str(i)]
            yield from numbers(i+1)
        
        def fizzies(source=numbers()):
            yield next(source)
            yield next(source)
            yield [*next(source), 'Fizz']
            yield from fizzies(source)
        
        def buzzies(source=fizzies()):
            yield next(source)
            yield next(source)
            yield next(source)
            yield next(source)
            yield [*next(source), 'Buzz']
            yield from buzzies(source)
        
        def main(source=buzzies()):
            terms = next(source)
            print(''.join(terms[1-len(terms):]))
            main(source)
        
        main()
  • somat3 days ago

    Enumerating all values probably can't be done in python as that requires some sort of unchecked loop construct, that is a goto or bare loop nether of which is present in python. perhaps a recursive solution(throws up a little in mouth)

    baring that I too got nerd sniped by this and unsatisfied by the limitations of the authors solution here is my attempt. and when I read up on fizzbuz to make sure I was solving the correct thing. (I was not and my elegant duel state engine was wasted) it turns out the problem solution could be as simple as

        f_out = ['', '', 'fizz']
        b_out = ['', '', '', '', 'buzz']
        
        def fizz_buz(n):
            return(f_out[n % 3] +  b_out[n % 5])
    
    anyhow the rest of my clever but unneeded and useless enumeration system, remember to read the spec first.

        f_state = {
            0:1,
            1:2,
            2:0,
            }
    
        b_state = {
            0:1,
            1:2,
            2:3,
            3:4,
            4:0,
            }
    
       def fizz_buzz_all():
            f_index = 0
            b_index = 0
            while 1: #how to loop with no end check?
                print(f_out([f_index] + b_out[b_index] )
                f_index = f_state[f_index]
                b_index = b_state[b_index]
    
    and the recursive solution:

        def fizz_buzz_recurse(n):
            print(fizz_buzz(n))
            fizz_buzz_recurse(n + 1)
    • soxfox423 days ago |parent

      That solution fails for any value that is a multiple of neither 3 nor 5. In those cases, the result should be the original number.

      • somat3 days ago |parent

        Sigh, Even after I reread the spec... I did not in fact read the spec. complete failure on my part.

    • jeremysalwen3 days ago |parent

      Make it throw an exception with an index out of bounds to terminate the loop.

    • drdeca3 days ago |parent

      You can replace `while 1:` with `for x in iter(int, 1):` .

  • seanhunter2 days ago

       from itertools import cycle
       
       fizz = cycle(["","","fizz"])
       buzz = cycle(["","","","","buzz"])
       for z in zip(fizz,buzz):
          print(f"{z[0]}{z[1]}")
    • bjoli2 days ago |parent

      When I find a problem I can solve with ciclical structures I always feel like Da Vinci.

      Fizzbuzz is probably the perfect fit, which is sad since it is a synthetic problem. I did find one problem once where I could save about 30s of user waiting time by generating (had to be done dynamically) wheels in the 3-10k element range.

    • throwaway1502 days ago |parent

      Doesn't look right. The numbers are missing from the output. If you don't print the numbers, sure it's trivial.

      • seanhunter2 days ago |parent

        I didn't know that was a requirement. OK then

           from itertools import cycle
           
           fizz = cycle(["","","fizz"])
           buzz = cycle(["","","","","buzz"])
           for idx, z in enumerate(zip(fizz,buzz)):
              print(f"{idx}: {z[0]}{z[1]}")
        
        
        Happy now?
        • throwaway1502 days ago |parent

          This doesn't look right either. You should really look up what FizzBuzz is. The output should be like:

            1
            2
            fizz
            4
            buzz
            fizz
          
          You can't just print the number for every line. Check this out - https://rosettacode.org/wiki/FizzBuzz or just run the program in the OP which prints correct output.
          • seanhunter2 days ago |parent

            I should really look it up because it’s such an important thing to get canonically correct.

            • throwaway1502 days ago |parent

              It's not important to look it up. I only suggested it because you posted two programs and neither solve the problem people are discussing here. If you really want to solve a boring version of the problem, by all means you should. I'm nobody to tell you otherwise.

              All I'm saying is that if you water the problem down into something trivial and boring, the solution will be trivial and boring too. No surprise there. The other answers here are more complicated because they solve the canonical FizzBuzz problem, not the boring version of it that is trivial to solve without conditionals.

              • genewitch2 days ago |parent

                it's more that fizzbuzz i think is one of the - if not the - simplest way to see if people understand requirements. some of the comments here remind me of thedailywtf.com comment sections.

            • nh23423fefe2 days ago |parent

              being correct is good and being confused is bad

  • kstrauser3 days ago

    How about:

      print(filter(None, [f + b, str(n)])[0])
    
    Would that be not-Boolean enough?
  • bluGill3 days ago

    I always wanted to write this with duff's device. switch with fall through is almost never a good thing but it allows for some 'interesting' tricks. Wouldn't be hard, but I have kids so finding half an hour to concentrate is hard.

  • stop509 days ago

    Answer: Modulo or adding "%3" and "%5" before masking it

  • LanceH3 days ago

    package main

      import (
       "fmt"
       "math/rand"
      )
    
      var fb [4]string = [4]string{"", "fizz", "buzz", "fizzbuzz"}
      var lucky int64 = 176064004
    
      func main() {
       for i := 1; i <= 100; i++ {
        if i%15 == 1 {
         rand.Seed(lucky)
        }
        fmt.Printf("%d: %s\n", i, fb[rand.Int63()%4])
       }
      }
    • frosting13373 days ago |parent

      There's a conditional, though?

      • albedoa3 days ago |parent

        Yeah this is weird. It's against the rules to speculate about whether a commenter read the article, but what about the title of the article?

        • LanceH3 days ago |parent

          I did, in fact, read the article. I've offered my subversive FizzBuzz as an alternative. It's not according to the article's rules, but it's definitely against the grain of the normal FizzBuzz.

  • swisniewski3 days ago

    Sigh…

    Saying the code doesn’t have conditions or booleans is only true if you completely ignore how the functions being called are being implemented.

    Cycle involves conditionals, zip involves conditionals, range involves conditionals, array access involves conditionals, the string concatenation involves conditionals, the iterator expansion in the for loop involves conditionals.

    This has orders of magnitude more conditionals than normal fizz buzz would.

    Even the function calls involve conditionals (python uses dynamic dispatch). Even if call site caching is used to avoid repeated name lookups, that involves conditionals.

    There is not a line of code in that file (even the import statement) that does not use at least one conditional.

    So… interesting implementation, but it’s not “fizzbuzz without booleans or conditionals”.

    • swisniewski3 days ago |parent

      Not sure why this got downvoted.

      The technique could be implemented without conditionals, but not in python, and not using iterators.

      You could do it in C, and use & and ~ to make the cyclic counters work.

      But, like I mentioned, the code in the article is very far from being free of conditionals.

      • abustamam3 days ago |parent

        I didn't down vote, but it does seem like unnecessary pedantry. Maybe it could be better phrased as "without writing any conditionals"

    • kstrauser3 days ago |parent

      I think that’s kind of vacuously true. Like, good luck writing this in any language where the resulting assembler all the way at the bottom of the runtime has zero branch operations. And I bet even then that most CPUs’ microcode or superscalar engine would have conditionals underlying the opcodes.

      I’d settle for just not writing conditionals in the user’s own code. Range doesn’t have to be implemented with branches. Hypothetically, Python could prefill a long list of ints, and range could return the appropriate slice of it. That’d be goofy, of course, but the main idea is that the user doesn’t know or really care exactly how range() was written and optimized.

  • cestith3 days ago

    What exactly are we counting as “a conditional”? Is it only “if” statements? Do “case” or “switch” statements count? Do loops with loop conditions count? Do all the included functions being abused count for all the conditionals in them? Do short-circuited boolean operations count, or only boolean variables?

    I mean, if we want to play fast and loose with those definitions then this also has no conditionals and no booleans.(Warning: Perl, somewhat golfed)

      $s = '', $i % 3 || ($s .= 'fizz'), $i % 5 || ($s .= 'buzz'), $s ||= $i, print "$s\n" while (++$i < 101)
  • Mikhail_Edoshin3 days ago

    What is the canonical formulation of FizzBuzz?

    • genewitch2 days ago |parent

      i've seen worse than this sort of thing:

      https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...

  • mapehe3 days ago

    This is pretty cool, actually

  • qrian2 days ago

    itertools.count is probably what OP is looking for