7

I'm still learning Haskell, and I was wondering if there is a less verbose way to express the below statement using 1 line of code:

map (\x -> (x, (if mod x 3 == 0 then "fizz" else "") ++ 
 if mod x 5 == 0 then "buzz" else "")) [1..100]

Produces: [(1,""),(2,""),(3,"fizz"),(4,""),(5,"buzz"),(6,"fizz"),(7,""),(8,""),(9,"fizz"),(10,"buzz"),(11,""),(12,"fizz"),(13,""),(14,""),(15,"fizzbuzz"),(16,""),(17,""),(18,"fizz"),(19,""),(20,"buzz"),(21,"fizz"),(22,""),(23,""),(24,"fizz"),(25,"buzz"),(26,""),(27,"fizz"),(28,""),(29,""),(30,"fizzbuzz"), etc

It just feels like I'm fighting the syntax more than I should. I've seen other questions for this in Haskell, but I'm looking for the most optimal way to express this in a single statement (trying to understand how to work the syntax better).

cdlane
  • 40,441
  • 5
  • 32
  • 81
Jonathan Dunlap
  • 2,581
  • 3
  • 19
  • 22

8 Answers8

10

We need no stinkin' mod...

zip [1..100] $ zipWith (++) (cycle ["","","fizz"]) (cycle ["","","","","buzz"])

or slightly shorter

import Data.Function(on)

zip [1..100] $ (zipWith (++) `on` cycle) ["","","fizz"] ["","","","","buzz"]

Or the brute force way:

zip [1..100] $ cycle ["","","fizz","","buzz","fizz","","","fizz","buzz","","fizz","","","fizzbuzz"]
Landei
  • 54,104
  • 13
  • 100
  • 195
7

If you insist on a one-liner:

[(x, concat $ ["fizz" | mod x 3 == 0] ++ ["buzz" | mod x 5 == 0]) | x <- [1..100]]
hammar
  • 138,522
  • 17
  • 304
  • 385
  • this seems very close to what I'm thinking. Any way to boil this down so that concat isn't needed? Reading it logically, the concat is a form of behavioral noise to me. It reads: make an array that steps 1 to 100 with each element being concat with two arrays containing a single string element pushed onto each other (..etc). – Jonathan Dunlap Jan 28 '12 at 21:37
  • 2
    By the way, lists in Haskell are singly-linked lists, not arrays; arrays (such as those in the [array](http://hackage.haskell.org/package/array) and [vector](http://hackage.haskell.org/package/vector) packages) are used relatively infrequently compared to other languages. – ehird Jan 28 '12 at 22:01
  • marking this as the answer as it reads the best as a 1-liner statement without compounding statements like 'where'. I also like that the expression is built entirely inside a list definition giving it more semantic context. – Jonathan Dunlap Jan 29 '12 at 00:07
  • You could use `join` instead of `concat` – pat Jan 29 '12 at 06:37
  • 1
    If you want to get rid of `concat`, replace the corresponding expression with: `[f | f <- "fizz", mod x 3 == 0] ++ [b | b <- "buzz", mod x 5 == 0]`. hammar's abuse of list comprehension is now even worse. – Rafael Caetano Jan 30 '12 at 04:06
  • @Rafael Can you explain why this is an abuse? Without knowing much about it, it seems to read very nicely. – Jonathan Dunlap Jan 30 '12 at 20:24
  • Hmm, if you don't know much about it then I think it's best to read more idiomatic examples first. One example is `[n | n <- [1..100], isPrime n]`. This is the increasing sequence of all integers between 1 and 100 (inclusive) that are prime. The guard refers to the same `n` that is bound in the generator. But in our examples, the value in the guard depends only on something defined outside the list comprehension. And my example takes advantage of the fact that `String` is just a list of `Char`s. I'm not criticizing hammar's answer, by the way. You asked for a short version, not a good one.;-) – Rafael Caetano Feb 01 '12 at 15:13
4

How's about...

fizzBuzz  =  [(x, fizz x ++ buzz x) | x <- [1..100]]
  where fizz n | n `mod` 3 == 0  =  "fizz"
               | otherwise       =  ""
        buzz n | n `mod` 5 == 0  =  "buzz"
               | otherwise       =  ""
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
2

Couldn't resist going in the other direction and making it more complicated. Look, no mod...

merge as@(a@(ia,sa):as') bs@(b@(ib,sb):bs') =
  case compare ia ib of
    LT -> a : merge as' bs
    GT -> b : merge as  bs'
    EQ -> (ia, sa++sb) : merge as' bs'
merge as bs = as ++ bs

zz (n,s) = [(i, s) | i <- [n,2*n..]]
fizzBuzz = foldr merge [] $ map zz [(1,""), (3,"fizz"), (5,"buzz")]
pat
  • 12,587
  • 1
  • 23
  • 52
1

Just for studying

zipWith (\a b -> b a) (map show [1..100]) $ cycle [id,id,const "fizz",id,const "buzz",const "fizz",id,id,const "fizz",const "buzz",id,const "fizz",id,id,const "fizzbuzz"]

produces

["1","2","fizz","4","buzz","fizz","7","8","fizz","buzz","11","fizz","13","14","fizzbuzz","16","17","fizz","19","buzz","fizz","22","23","fizz","buzz","26","fizz","28","29","fizzbuzz","31","32","fizz","34","buzz","fizz","37","38","fizz","buzz","41","fizz","43","44","fizzbuzz","46","47","fizz","49","buzz","fizz","52","53","fizz","buzz","56","fizz","58","59","fizzbuzz","61","62","fizz","64","buzz","fizz","67","68","fizz","buzz","71","fizz","73","74","fizzbuzz","76","77","fizz","79","buzz","fizz","82","83","fizz","buzz","86","fizz","88","89","fizzbuzz","91","92","fizz","94","buzz","fizz","97","98","fizz","buzz"]
Sergei Iashin
  • 116
  • 1
  • 4
1

Along the same lines as larsmans' answer:

fizzBuzz = [(x, f 3 "fizz" x ++ f 5 "buzz" x) | x <- [1..100]]
  where f k s n | n `mod` k == 0 = s
                | otherwise      = ""
dave4420
  • 46,404
  • 6
  • 118
  • 152
  • that's essentially what I just did: `[ (x, "Fizz" \`ifDivisibleBy\` 3 ++ "Buzz" \`ifDivisibleBy\` 5) | x <- [1..100], let ifDivisibleBy s n = if x \`mod\` n == 0 then s else "" ]` – rampion Jan 28 '12 at 19:52
1

I think the reason why you feel like you are fighting the syntax is because you are mixing too many types.

Instead of trying to print:

[(1, ""), (2,""), (3,"Fizz")...]

Just think of printing strings:

["1","2","Fizz"...]

My attempt:

Prelude> let fizzBuzz x | x `mod` 15 == 0 = "FizzBuzz" | x `mod` 5 == 0 = "Buzz" | x `mod` 3 == 0 = "Fizz" | otherwise = show x
Prelude> [fizzBuzz x | x <-[1..100]]

["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"...]

In order to convert an Int to String you use the:

show x
elviejo79
  • 4,592
  • 2
  • 32
  • 35
0

Writer monad may look nice (if you don't like concat):

fizzBuzz = [(x, execWriter $ when (x `mod` 3 == 0) (tell "fizz") >> when (x `mod` 5 == 0)  (tell "buzz")) | x <- [1..100]]

It's not particularly succinct though.

Rotsor
  • 13,655
  • 6
  • 43
  • 57