11 ms·
Python can produce extensible solutions! It can! (Edit from sillier comment.) def fizzbuzz(n): outstring = "" outputs = {5: "buzz", 3: "fizz",
by paultopia 9y ago
Python can produce extensible solutions! It can!
(Edit from sillier comment.)
def fizzbuzz(n):
outstring = ""
outputs = {5: "buzz", 3: "fizz", 7: "bar"}
for x in sorted(outputs.keys()):
if n % x == 0:
outstring += outputs[x]
if outstring:
return outstring
return n
Then all you gotta do to add more prime numbers is stick them in the dict (or factorize composite numbers you want to put in). Which is more idiomatic in python than endless if elif elif elif elif.
- deleted 9y ago[deleted]
- alanbernstein 9y agoYea, I'm all for overthinking things, but as a solution to fizzbuzz, this article is silly. You don't need any complicated control flow or logic, or functional primitives, for an extensible, readable fizzbuzz.
- KirinDave 9y agoWell, of course you do not "need" solid engineering to express the solution to a trivial problem. The point is not what you need to solve fizzbuzz, it's to give people a taste of what it feels like to do so. And the result is: "oh, it feels normal, but I write less of my own code and reuse existing control flow code." Which is a lot of the point. It's also to point out simple abstractions like Monoid can find uses even in simple problems.
- Veedrac 9y agoLess? Even ignoring the glut of imports, it's significantly longer than the straightforward and more extensible attempt where you just chain a few `if`s. (The article did strawman that attempt, but it was transparent enough about it.)
- KirinDave 9y agoWhich if my examples is it longer than? It's been awhile since I wrote this so...
- Veedrac 9y agoNone of the examples you specifically gave. As I said, it strawman'd the easy method.
- KirinDave 9y agoI don't know what the "easy" method you're referring to is. I did include an else-if chain and did my best to not inflate it. Even though it was 5 years ago I need to revisit this blog to update the pronouns anyways, so please do correct me.
- Veedrac 9y agoloop i = 0..100 { out = "" if i % 3 == 0 { out += "Fizz" } if i % 5 == 0 { out += "Fizz" } if i % 7 == 0 { out += "Fizz" } if out == "" { out = str(i) } display out }
- KirinDave 9y agoIs this really much shorter than my Haskell example? If we count the import decls it seems to be a bit of a wash.
- Veedrac 9y agoIt's about 0.7x the length of the final Haskell version, ignoring indentation and imports, but I don't really care about golfing. My point was simply that your statement that you "write less of [your] own code" was not justified.
- 9y ago
- rbehrends 9y agoThe thing is, you're not only complicating your code by introducing monoids, you're unnecessarily restricting yourself. The only thing you need is higher-order functions to be sufficiently general: let every m s i = if i mod m = 0 then Some s else None let fallback i l = if l = [] then [ string_of_int i ] else l let join l = String.concat "" l let fizzbuzz gens i = List.filter_map (fun f -> f i) gens |> fallback i |> join let main () = let fizzbuzzstd = fizzbuzz [ every 5 "Fizz"; every 3 "Buzz" ] in for i = 1 to 100 do print_endline (fizzbuzzstd i) done I've pulled out `join` and `fallback` as separate functions to show that you can also parameterize the code over these functions. And you can use other patterns instead of "every n-th entry" by using generators other than `every`. This approach can make joining O(n) as opposed to O(n^2) (naive concatenation) or O(n*log(n)) (ropes) worst case and is more general. For example, you can also generate numbered lists for each line ("1. Fizz 2. Buzz"). Yeah, that's silly for FizzBuzz, but we're sort of assuming that this is a placeholder for a real problem that we may want to generalize or tune for performance. And if it's actually a monoid and we know nothing more about it than that, we can just go with `List.fold_left` as the join function. Works for all monoids, too, without mentioning the word.
- KirinDave 9y agoFirst of all, thanks for your post. I don't think your code is wrong or bad in any way. > The thing is, you're not only complicating your code by introducing monoids, you're unnecessarily restricting yourself. Strictly speaking? I agree. All we need for this problem is semigroups. We never explicitly need a "monoid." But I'm not sure offering the "mempty" case is really over-complicating the code, it just bloats the rhetoric a wee bit. But I think what you're referring to is the conceptual overhead of Monoids? I'll get to that in a sec. > The only thing you need is higher-order functions to be sufficiently general: > code follows For "sufficiently" general code, it certainly seems to be less general than a piece of code relying on monoid or semigroup operators. You've hardwired yourself to strings and arrays. F# actually is doing a natural transformation for you from (Maybe~>Maybe String) implicitly in there, so you're really relying on the fact that F#'s got some monoid logic baked in. > And you can use other patterns instead of "every n-th entry" by using generators other than `every`. Right, the essence of fizzbuzz is in the list of partial applications of every onto a list of prime factors and strings. Everything else is repetitive joining logic. Which is why appealing to a pattern that captures nearly all that logic seems like a good idea, no? > This approach can make joining O(n) as opposed to O(n^2) (naive concatenation) or O(n*log(n)) (ropes) worst case and is more general. I do not see how it's more general unless you demand non-associativity which—while fair to do in an interview question—is quite unheard of in the programming world. In terms of runtime performance, rope concatenation is constant and a full retrieval is O(n + log n), not O(n log n), so I'm not sure what you're getting here. But it's worth noting this code (like a lot of F# code) tends to pin onto exactly one data structure and can't simply imply the data structure from calling types. > Works for all monoids, too, without mentioning the word. There's an implicit value judgement here: that not naming this pattern is better. You mentioned it above with the "you complicate your code by introducing monoids." I'm curious why the default mode is that I need to defend naming a pattern that obviously saves code here, and captures more use cases. I don't mean this to be a contentious discussion, I just want to know why you think it's inherently bad.
- whoosh99 9y agoWhoosh.
- blixt 9y agoHold up, "idiomatic Python" and no generator comprehensions?! def fizzbuzz(i): outputs = [(3, 'Fizz'), (5, 'Buzz'), (7, 'Bar')] return ''.join(word for x, word in outputs if i % x == 0) or str(i)
- KirinDave 9y agoIt was over 5 years ago and these are more accepted now.
- blixt 9y agoSorry it wasn't meant very seriously, just me expressing a caricature of idiomatic Python :) So disclaimer: good old `for` loops are often a better choice unless you're doing a simple modification or filtering of another sequence.
- svara 9y agoReally? I actually liked that solution, I think it's both readable and clever. The rule priority is defined by the order of your list of tuples ('fizzbuzz' vs. 'buzzfizz'), so easy to change. That's superior to the solution above with sorted(outputs.keys()).
- blixt 9y agoI would say my solution is borderline. It requires maybe one or two extra reads to grok in its entirety due to multiple conditionals, one of which is in a loop and the other outside. Here's a more readable version for someone looking at it the first time, or who is not too used to Python: OUTPUTS = [(3, 'Fizz'), (5, 'Buzz'), (7, 'Bar')] def fizzbuzz(i): result = '' for x, word in OUTPUTS: if i % x == 0: result += word return result or str(i) Even the final `or` could be broken out to an `if not`, but in this case it's so simple I prefer putting it together, much like a coalescing operator. And then for shits and giggles we can say screw it and put it all in one line: print '\n'.join(''.join(word for x, word in [(3, 'Fizz'), (5, 'Buzz'), (7, 'Bar')] if i % x == 0) or str(i) for i in xrange(1, 101))
- KirinDave 9y agoAssociative arrays form a monoid, so this code is (perhaps surprisingly) a special case of the monoid library. It has been awhile, but I remember not including a version like this because it's a bit intense to throw a sort (either implicit or explicit) into this without even commenting on it.
- imtringued 9y agoYou can form a monoid with almost everything. It'S not a very interesting concept that requires more attention than maybe reading the definition. These blogposts are pointless in my opinion. The title makes as much sense as "Overthinking cooking with edibles".
- KirinDave 9y agoMonoids are certainly not the most "interesting" thing, but neither are arrays, or even sets. But they're quite important and useful in a parametrically polymorpic world. That's why it's worth introducing them to people new to FP. Ultimately, these sorts of posts are feeders to conversations about other bigger abstractions like Functors. The title and the post both grant that for fizzbuzz as asked in many settings, but without a simple domain these conversations tend to explode in complexity. So there it is.