168 pointsby hprotagonist19 hours ago18 comments
  • nine_k16 hours ago
    Well, there must be an obvious solution where the fizzbuzz sequence is seen as a spectrum of two frequencies (1/3 and 1/5), and a Fourier transform gives us a periodic signal with peaks of one amplitude at fizz spots, another amplitude at buzz spots, and their sum at fizzbuzz spots. I mean. that would be approximately the same solution as the article offers, just through a more straightforward mechanism.
    • susam16 hours ago
      That is precisely how I began writing this post. I thought I'd demonstrate how to apply the discrete Fourier transform (DFT) but to do so for each of the 15 coefficients turned out to be a lot of tedious work. That's when I began noticing shortcuts for calculating each coefficient c_k based on the divisibility properties of k. One shortcut led to another and this post is the end result. It turns out it was far less tedious (and more interesting as well) to use the shortcuts than to perform a full-blown DFT calculation for each coefficient.

      Of course, we could calculate the DFT using a tool, and from there work out the coefficients for the cosine terms. For example, we could get the coefficients for the exponential form like this:

      https://www.wolframalpha.com/input?i=Fourier%5B%7B3%2C+0%2C+...

      And then convert them to the coefficients for the cosine form like this:

      https://www.wolframalpha.com/input?i=%7B11%2F15%2C+2*0%2C+2*...

      That's certainly one way to avoid the tedious work but I decided to use the shortcuts as the basis for my post because I found this approach more interesting. The straightforward DFT method is perfectly valid as well and it would make an interesting post by itself.

      • susam12 hours ago
        Update: I went ahead and added the method of obtaining the coefficients using DFT anyway. Like I mentioned above, this approach is quite tedious by hand, so I only work out the first few coefficients explicitly. In practice, these are almost always computed using numerical software. But for some people it may still be interesting to see a direct calculation rather than relying on shortcuts.

        Here is the direct link to the new section on DFT: https://susam.net/fizz-buzz-with-cosines.html#dft

        • xbar10 hours ago
          This is a joy.
    • mr_wiglaf14 hours ago
      Ah so taking the Fourier transform of this function[0]? The summation of the fizz and buzz frequencies don't lead to perfect peaks for the fizz and buzz locations. I need to revisit Fourier cause I would have thought the transform would have just recovered the two fizz and buzz peaks not the fizzbuzz spot.

      [0]: https://www.desmos.com/calculator/wgr3zvhazp

    • 13 hours ago
      undefined
    • atemerev16 hours ago
      Yes. Exactly. This is how it _should_ have been done.

      Also probably easy enough to encode as quantum superpositions.

      • HPsquared15 hours ago
        How would someone do FizzBuzz on a quantum computer? It seems like a nice toy example problem.
  • thomasjudge18 hours ago
  • user0702234 hours ago
    Inspired by this post & TF comment I tried symbollic regression [0] Basically it uses genetic algorithm to find a formula that matches known input and output vectors with minimal loss I tried to force it to use pi constant but was unable I don't have much expreience with this library but I'm sure with more tweaks you'll get the right result

      from pysr import PySRRegressor
    
      def f(n):
          if n % 15 == 0:
              return 3
          elif n%5 == 0:
              return 2
          elif n%3 == 0:
              return 1
          return 0
    
      n = 500
      X = np.array(range(1,n)).reshape(-1,1)
      Y = np.array([f(n) for n in range(1,n)]).reshape(-1,1)
      model = PySRRegressor(
              maxsize=25,
              niterations=200,  # < Increase me for better results
              binary_operators=["+", "*"],
              unary_operators=["cos", "sin", "exp"],
              elementwise_loss="loss(prediction, target) = (prediction - target)^2",
    )

      model.fit(X,Y)
    
    Result I got is this:

    ((cos((x0 + x0) * 1.0471969) * 0.66784626) + ((cos(sin(x0 * 0.628323) * -4.0887628) + 0.06374673) * 1.1508249)) + 1.1086457

    with compleixty 22 loss: 0.000015800686 The first term is close to 2/3 * cos(2pi*n/3) which is featured in the actual formula in the article. the constant doesn't compare to 11/15 though

    [0] https://github.com/MilesCranmer/PySR

    • Quarrel2 hours ago
      Great work, I really liked Susam's setup in the article:

      > Can we make the program more complicated? The words 'Fizz', 'Buzz' and

      > 'FizzBuzz' repeat in a periodic manner throughout the sequence. What else is

      > periodic?

      and then I'm thinking ..

      > Trigonometric functions!

      is a good start, but there are so many places to go!

  • isoprophlex16 hours ago
    What a neat trick. I'm thinking you can abuse polynomials similarly. If the goal is to print the first, say, 100 elements, a 99-degree polynomial would do just fine :^)

    EDIT: the llm gods do recreational mathematics as well. claude actually thinks it was able to come up with and verify a solution...

    https://claude.ai/share/5664fb69-78cf-4723-94c9-7a381f947633

    • jiggawatts13 hours ago
      That's the most expletive-laden LLM output I've ever seen. ChatGPT would have aborted half way through to protect its pure and unsullied silicon mind from the filthy impure thoughts.
      • flir2 hours ago
        > LMAOOOOO OKAY SO THE POLYNOMIAL IS LITERALLY SHITTING ITSELF

        That was a fun read, but I can see that persona quickly becoming wearing. I had a "talk like a wiki article" persona for a while that worked better (for me) than any attempt to inject personality. The greyer the better, when it comes to tools.

        (Being a child of the internet rather than the classroom my abusive solution would be to look up the sequence in OEIS, but I think fizzbuzz could be encoded into an L-system quite neatly).

        • isoprophlexan hour ago
          Yes, it's indeed very over the top and one-dimensional. However, I've been iterating on this system prompt since the early early days of chatgpt.com, and I find that I can't really chat with AI systems in their "grey", dry mode anymore.

          On their default behavior they try too hard to make me like them, which I find intolerable. "You're absolutely right!" for some reason drives me insane; getting "lmaaoooo my bad fam i dun goofed" twenty times a day is equally annoying in terms of models being confidently wrong, but somehow the lazy shitbag attitude pisses me off less than the goody two shoes energy.

          And if they're low on crazyness and you force them to be crisp and emotionless like a wikipedia article, I notice that I tend to trust them more... even though again the tendency to bullshit is unchanged, still there.

          Somehow this really works for me.

          Also when coding it makes it very clear which bits I haven't inspected yet because the comments and variable names will be super nsfw, thus keeping me on my toes as to not accidentally submit PRs filled with "unfuck_json()" functions.

      • theendisney9 hours ago
        It would find a therapist contact your employer, your wife and your dad.
    • mikestaas9 hours ago
      absolute madlad
  • pillars0015 hours ago
    HN is a great place to learn non-trivial things about trivial things, and that’s why I like it. My comment won’t add much to the discussion, but I just wanted to say that I learned something new today about a trivial topic I thought I already understood. Thank you, HN, for the great discussion thread.
  • Someone4 hours ago
    So, there’s a similar way to do it with a function that produces one of the characters in “FizBu\nx” and a while true loop that

    - increases i on every \n,

    - prints i when that produces x, otherwise prints the character

    (Disregarding rounding errors)

    That would be fairly obfuscated, I think.

  • ok12345616 hours ago
    I once had a coworker who used the FFT to determine whether coordinates formed a regular 2D grid. It didn't really work because of the interior points.
  • layer817 hours ago
    I think that implementation will break down around 2^50 or so.
  • raffael_de4 hours ago
    This seems like a great benchmark task for LLMs.
  • siegelzero17 hours ago
    Very cool! There's definitely some similarity to Ramanujan Sums, though the approach here sort of packages the fizz-buzz divisibility properties into one function. https://en.wikipedia.org/wiki/Ramanujan%27s_sum
  • econ10 hours ago
    Made me envision this terrible idea.

    arr = [];

    y = 0;

    setInterval(()=>{arr[y]=x},10)

    setInterval(()=>{x=y++},1000)

    setInterval(()=>{x="fizz"},3000)

    setInterval(()=>{x="buzz"},5000)

    setInterval(()=>{x="fizzbuzz"},15000)

  • 18 hours ago
    undefined
  • ivan_ah18 hours ago
    This is very nice.
  • jmclnx13 hours ago
    I wonder where this is coming from. I saw on USENET in comp.os.linux.misc a conversation about fizzbuzz too. That was on Nov 12.

    Anyway an interesting read.

    • acheron7 hours ago
      You saw a Usenet post on Nov 12? 2025?
  • burnt-resistor13 hours ago
    While it's cute use of mathematics, it's extremely inefficient in the real world because it introduces floating point multiplications and cos() which are very expensive. The only thing it lacks is branching which reduces the chances of a pipeline stall due to branch prediction miss.

    (The divisions will get optimized away.)

    • pbsd10 hours ago
      This can be translated to the discrete domain pretty easily, just like the NTT. Pick a sufficiently large prime with order 15k, say, p = 2^61-1. 37 generates the whole multiplicative group, and 37^((2^61-2)/3) and 37^((2^61-2)/5) are appropriate roots of unity. Putting it all together yields

          f(n) = 5226577487551039623 + 1537228672809129301*(1669582390241348315^n + 636260618972345635^n) + 3689348814741910322*(725554454131936870^n + 194643636704778390^n + 1781303817082419751^n + 1910184110508252890^n) mod (2^61-1).
      
      This involves 6 exponentiations by n with constant bases. Because in fizzbuzz the inputs are sequential, one can further precompute c^(2^i) and c^(-2^i) and, having c^n, one can go to c^(n+1) in average 2 modular multiplications by multiplying the appropriate powers c^(+-2^i) corresponding to the flipped bits.
      • burnt-resistor5 hours ago
        Integer exponentiation is still really, really expensive. 3-4 modulus operations and a few branches is a lot cheaper.
  • tantalor18 hours ago
    There are several mentions of "closed-form expression" without precisely defining what that means, only "finite combinations of basic operations".

    TFA implies that branches (if statements and piecewise statements) are not allowed, but I don't see why not. Seems like a basic operation to me.

    Nevermind that `s[i]` is essentially a piecewise statement.

    • susam17 hours ago
      > There are several mentions of "closed-form expression" without precisely defining what that means, only "finite combinations of basic operations".

      There is no universal definition of 'closed-form expression'. But there are some basic operations and functions that are broadly accepted, and they are spelled out directly after the 'finite combinations' phrase you quoted from the post. Quoting the remainder of that sentence here:

      '[...] finite combinations of basic operations such as addition, subtraction, multiplication, division, integer exponents and roots with integer index as well as functions such as exponentials, logarithms and trigonometric functions.'

  • Terretta12 hours ago
    The article conceit is fantastic. That said, is the going-in algo wrong?

    I see a case for 3 * 5 in here:

      for n in range(1, 101):
          if n % 15 == 0:
              print('FizzBuzz')
          elif n % 3 == 0:
              print('Fizz')
          elif n % 5 == 0:
              print('Buzz')
          else:
              print(n)
    
    Why?

    If we add 'Bazz' for mod 7, are we going to hardcode:

      for n in range(1, 105):
          if n % 105 == 0:          # 3 * 5 * 7
              print('FizzBuzzBazz')
          elif n % 15 == 0:         # 3 * 5
              print('FizzBuzz')
          elif n % 21 == 0:         # 3 * 7
              print('FizzBazz')
          elif n % 35 == 0:         # 5 * 7
              print('BuzzBazz')
          elif n % 3 == 0:
              print('Fizz')
          elif n % 5 == 0:
              print('Buzz')
          elif n % 7 == 0:
              print('Bazz')
          else:
              print(n)
    
    Or should we have done something like:

      for n in range(1, 105):
          out = ''
      
          if n % 3 == 0:
              out += 'Fizz'
          if n % 5 == 0:
              out += 'Buzz'
          if n % 7 == 0:
              out += 'Bazz'
      
          print(out or n)
    
    I've been told sure, but that's a premature optimization, 3 factors wasn't in the spec. OK, but if we changed our minds on even one of the two factors, we're having to find and change 2 lines of code ... still seems off.

    Sort of fun to muse whether almost all FizzBuzz implementations are a bit wrong.

    • michaelcampbell10 hours ago
      > Sort of fun to muse whether almost all FizzBuzz implementations are a bit wrong.

      They're only wrong if they provide output that isn't in the spec. Adding "bazz" isn't in the spec, and assuming that something indeterminate MIGHT come later is also not part.

      • Terretta9 hours ago
        Yep, that's how people answer.

        Folks really really don't like thinking that "FizzBuzz" case maybe shouldn't be there, future extension or factor edit or no.

        // And as long as we're just manually computing factor times factor and typing out the results for it like "FizzBuzz" we might as well just hardcode the whole series...

        • theendisney9 hours ago
          I think the reqirement should be to n digits. Then at least we can benchmark it.
    • 12 hours ago
      undefined
    • theendisney9 hours ago
      If we are going to be like that we should just increment a var by 3,5 or 7 and compare it rather than %3 as the later seems expensive.