cases = (
(3, 'Fizz'),
(5, 'Buzz'),
(7, 'Bazz'),
(11, 'Boo'),
(13, 'Blip'),
)
for i in range(1, 101):
out = []
for c in cases:
if i % c[0] == 0:
out.append(c[1])
if out:
print ''.join(out)
else:
print i
Edit: not to detract from the post's point, I think it's valid. Monoids are cool and all but simple counting arguments can take you a long, long, long, way when case analysis fails you.
This can be edited down to six lines with a simple trick: a Python string multiplied by True will return itself, and multiplied by False will return the empty string.
for i in range(110):
pr = ""
pr += "Fizz" * (i%3 == 0)
pr += "Buzz" * (i%5 == 0)
pr += "Bazz" * (i%7 == 0)
print (pr if pr else i)
I iterated over range(110) to show that it handles the "FizzBuzzBazz" case correctly.
EDIT: Or we could use lambdas as OP's Ruby code did; this might be more maintainable...
cases = [
lambda n: "Fizz" * (n%3 == 0),
lambda n: "Buzz" * (n%5 == 0),
lambda n: "Bazz" * (n%7 == 0) ]
for i in range(110):
pr = ""
for case in cases:
pr += case(i)
print (pr if pr else i)
I agree. Same thing in JS. Also, this is precisely what I thought to do when he started asking about 7 and 11.
(function fizzbuzz(n, cases) {
var i = 0;
while (i++ < n) {
console.log(cases.reduce(function (prev, c) {
return i % c[0] ? prev : (prev || '') + c[1];
}, null) || i);
}
}(100, [[3, 'Fizz'], [5, 'Buzz'], [7, 'Bazz'], [11, 'Boo'], [13, 'Blip']]));
Excuse the terseness, I didn't want to make this post too lengthy, but it's still quite readable, IMO. Array.reduce was designed to solve problems like this.
The Ruby solution author quotes as ideal and impressive seems way overblown to me. And I don't buy that ,,it would probably be dismissed as “overly complex” by younger programmers'' because it is exactly what I would consider perfect approach... few years ago. Since then I learned the value of simplicity, and abstractions with adequate flexibility.
Firstly, the ruby code was produced (admittedly from my memory) from someone on the tail end of a 5 hour interview process, of which I was not the first technical interviewer. It is exceptional in that context.
Secondly, I feel like you (and aristus in his/her code snippet above) missed the secondary point of my post. Please consider that monoids and Maybe are capturing a higher level pattern in a way that we can compose. To write a classical for loop and toss in conditionals and whatnot is to descend to exactly the same depths as the Ruby code you say is not "simple".
Basically, you say one is "simple" and the other is not is you picking up on what you're comfortable with in Python. Both are complex in the same way; the Python just makes a marginally better choice in how to represent the rules (as data).
If you'd like to see how I'd take that piece of code and golf it to include that feature, I'm happy to oblige. I originally was going to post that, but cut it because the post was already overlong and the point is to explore the unusual patterns fp is capturing.
Here is my bullshit golfing derivative, though:
{-# LANGUAGE MonadComprehensions #-}
module Main where
import Control.Applicative
import Data.Monoid
import Data.Maybe
import System.Environment
factors = [(3, "fizz"), (5, "buzz"), (7, "bazz")]
rules = [\i -> [res | i `rem` fac == 0] | (fac,res) <- factors]
fizzbuzz d i = fromMaybe (d i) $ mconcat (rules <*> pure i)
main = do
upTo <- fmap (maybe 100 read . listToMaybe) getArgs
mapM_ putStrLn [ fizzbuzz show i | i <- [1..upTo] ]
I'd like to understand more that second point. How is Maybe not morally equivalent to a conditional? If my ifs were in a separate function that would also work, no?
> How is Maybe not morally equivalent to a conditional?
Maybe is a way to represent conditionals that is amenable to higher order patterns. if-then-else conditionals are simply that, whereas Maybe is amenable to Monad, Monoid, and Applicative functor laws. So when we pull out mconcat or <> or mappend, we're actually talking about a higher order pattern.
As I mentioned in the article, we could take that same fizzbuzz code and modify it in many ways under the monoid rules. For example, a different harness in the main function could use Map String Integer and completely change the behavior with the same fizzbuzz function.
I had a bit more time to spare and thought I'd give you an example of this. First, let's rewrite fizzbuzz to totally get rid of any mention Maybe; we'll deal with it outside:
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
This is written in terms of an Applicative Functor and a Monoid.
And then can just do some haskell things. I've done less golfing here and more type signatures to make the code more approachable.
{-# LANGUAGE MonadComprehensions, OverlappingInstances, FlexibleInstances#-}
module Main where
import Control.Applicative
import Data.Monoid
import Control.Monad
import Data.Maybe
import Data.List
import qualified Data.HashMap.Strict as M
import System.Environment
-- Let's make it clear what we're working with.
type Counter = M.HashMap String Integer
-- We want an instance slightly different from the default.
instance Monoid Counter where
mempty = M.empty
mappend = M.unionWith (+)
factors = [(3, "fizz"), (5, "buzz"), (7, "bazz")]
-- Our rule function is slightly different.
-- Not unexpected, since our logic has changed. But we could generalize
-- this further!
rules :: [(Integer -> Maybe Counter)]
rules = [\i -> [M.singleton res 1 | i `rem` fac == 0] | (fac,res) <- factors]
-- Fizzbuzz remains unchanged.
fizzbuzz d i = mconcat (rules <*> pure i) <|> pure (d i)
main = do
upTo <- fmap (maybe 100 read . listToMaybe) getArgs
let results = foldl' mappend mempty [ fromJust $ fizzbuzz (const mempty) i | i <- [1..upTo] ]
putStrLn $ show results
And then a typical session:
~/P/h/fb-toys > time ./fbg3 10000000
fromList [("bazz",1428571),("fizz",3333333),("buzz",2000000)]
3.58 real 3.54 user 0.03 sys
Which is a pretty expensive way to avoid doing algebra, but the point is that we're talking about very high level patterns here for fizzbuzz. Fizzbuzz is probably a bad name here, it's more like mergeOptionalPatterns. I'm willing to bet if I dug a round a bit in parser combinator libraries I could find something that does nearly exactly this.
I confess I had to play with it a bit to get it to play nice with large inputs.
Halfway through the article, I'd write the Python as:
for i in xrange(1, 101):
s = ""
if not i % 3:
s += "Fizz"
if not i % 5:
s += "Buzz"
if not i % 7:
s += "Bazz"
print s or i
Which can be shortened to:
for i in xrange(1, 101):
s = ""
s += "Fizz" if i % 3 == 0 else ""
s += "Buzz" if i % 5 == 0 else ""
s += "Bazz" if i % 7 == 0 else ""
print s or i
Finishing the article inspired:
print "\n".join(
"".join(filter(None, ("Fizz" if i % 3 == 0 else None,
"Buzz" if i % 5 == 0 else None,
"Bazz" if i % 7 == 0 else None)))
or str(i) for i in xrange(1, 101))