fizzbuzz in haskell?
I'm trying to write 'fi开发者_开发问答zzbuzz' in haskell using list comprehensions.
Why doesn't the following work, and how should it be?
[ if x `mod` 5 == 0 then "BUZZFIZZ"
if x `mod` 3 == 0 then "BUZZ"
if x `mod` 4 == 0 then "FIZZ" | x <- [1..20],
x `mod` 3 == 0,
x `mod` 4 == 0,
x `mod` 5 == 0 ]
This isn't valid Haskell. The else
branch is not optional in if ... then ... else
. Rather than using if
, this seems like a good opportunity to use a case
statement.
case (x `rem` 3, x `rem` 5) of
(0,0) -> "fizzbuzz"
(0,_) -> "fizz"
(_,0) -> "buzz"
_ -> show x
This snippet will work for a traditional "fizzbuzz"; your code seems to be slightly different.
First of all, you're missing the else
parts of your if
expressions. In Haskell, if
is an expression, not a statement, so the else
part is mandatory.
Secondly, the list comprehension only produces any values if all the guard expressions evaluate to True
. There is no number between 1 and 20 that is 0 modulo 3, 4, and 5, so you'll get no results. You'll want to use ||
(logical OR) to combine them instead.
Third, most definitions of FizzBuzz want you to return the number itself if it does not meet any of the other conditions. In that case, you'll want to use show
to convert the number to a String
.
Here's another version that can be extended to an arbitrary number of substitutions:
fizzbuzz' :: [(Integer, String)] -> Integer -> String
fizzbuzz' ss n = foldl (\str (num, subst) -> if n `mod` num == 0 then str ++ subst else str ++ "") "" ss
fizzbuzz :: [(Integer, String)] -> Integer -> String
fizzbuzz ss n = if null str then show n else str
where str = fizzbuzz' ss n
You could inline fizzbuzz'
in the where
clause of fizzbuzz
, but I found a separate function easier for testing.
You can run it like this:
λ> mapM_ putStrLn $ map (fizzbuzz [(3, "fizz"), (5, "buzz")]) [9..15]
fizz
buzz
11
fizz
13
14
fizzbuzz
Or with extra substitutions:
λ> mapM_ putStrLn $ map (fizzbuzz [(3, "fizz"), (5, "buzz"), (7, "dazz")]) [19..24]
19
buzz
fizzdazz
22
23
fizz
In general, it helps if you give the error as well, not just the code.
But in this case, the problem is that every if
needs an else
clause. Keep in mind that an if
statement is just an expression, so both branches must return a value of an appropriate type.
You've got several bugs in the code, by the way, but that's the only compiler error.
Here is a response to the traditional FizzBuzz problem using list comprehension + guards.
fizzbuzz = [fb x| x <- [1..100]]
where fb y
| y `mod` 15 == 0 = "FizzBuzz"
| y `mod` 3 == 0 = "Fizz"
| y `mod` 5 == 0 = "Buzz"
| otherwise = show y
Or alternatively don't be afraid to move where
clause into separate succinct function for evaluating x
then call that function from your list comprehension. Its better to build on small concise functions than trying to solve it in one more complex function. e.g.
fizzval x
| x `mod` 15 == 0 = "FizzBuzz"
| x `mod` 3 == 0 = "Fizz"
| x `mod` 5 == 0 = "Buzz"
| otherwise = show x
fizzbuzz = [fizzval x| x <- [1..100]]
With the help of some judicious currying, Calvin Bottoms did it in 77 characters.
http://freshbrewedcode.com/calvinbottoms/2012/02/25/fizzbuzz-in-haskell/
[max(show x)(concat[n|(f,n)<-[(3,"Fizz"),(5,"Buzz")],mod x f==0])|x<-[1..25]]
The Monad Reader contains a solution as well as many valuable insights and some more fun exercises. The solution is copied here for those who just want to see it.
fizzbuzz :: Int -> String
fizzbuzz n = (test 3 "fizz" . test 5 "buzz") id (show n)
where test d s x | n `mod` d == 0 = const (s ++ x "")
| otherwise = x
There is no x that fullfills the condition to be divisible by 3, 4 and 5 in the range 1..20.
Therefore, you would get an empty list if the example were syntactically correct, which it is not.
精彩评论