Wednesday 20 May 2015

FizzBuzz

This example illustrates the use of the case construction.  An expression is  evaluated and the result is matched against a sequence of possible values to determine which further expression is to be evaluated to be the  return value of the whole expression.

The underscore character is a wildcard that matches anything.

The aim is to create the fizzbuzz functions, which takes a number and returns "Fizz" if the number is a multiple of 3, "Buzz" if the number is a multiple of 5, "Fizzbuzz" if the number is a multiple of 15, and otherwise just the number.

We can evaluate the mod of the number against both 3 and 5 at the top of the function and match the results in a tuple.  We don't have to check explicitly for a multiple of 15 because multiples of 15 are the numbers that are multiples of both 3 and 5.

fizzbuzz :: Int -> String
fizzbuzz x =
    case (x `mod` 3, x `mod` 5) of
      (0, 0) -> "Fizzbuzz"
      (0, _) -> "Fizz"
      (_, 0) -> "Buzz"
      (_, _) -> show x

You know that nagging feeling that there is a better way.  Caused by the fact that the text strings Fizz and Buzz show up in two places.

We can't return the number as the final clause of the function because the return value has to be a string - we use the function show to convert a string.

The command :load will load your code into the REPL.

Therefore the function fizzbuzz has type Int goes to string:

*Main> :t fizzbuzz
fizzbuzz :: Int -> String

And I can view thr fizzbuzzes from 1 to 100 in the REPL:

*Main> map fizzbuzz [1..100]
["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"]
*Main> 

OK, but instead of seeing these in the REPL I want to print them out.  Instead of mapping to a string I want to map fizzbuzz to an IO action that writes a string to the terminal, plus a line feed.  The library function I want is putStrLn:

*Main> :t putStrLn
putStrLn :: String -> IO ()

So the function to call fizzbuzz and get the IO action to write the reulting string to the screen is the combination of these, created by function composition operator which is a dot . thus

*Main> :t (putStrLn . fizzbuzz)
(putStrLn . fizzbuzz) :: Int -> IO ()

Right, so I want to map this combined function onto the integer list 1..100. We need the right version of the map function, though, this one:

*Main> :t mapM_
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

So mapM_ will take our function that goes from an Integer to an IO action, apply this to a list of Integers, and give us back an IO action that we can plug straight into the main function, like this:

main :: IO ()
main = do
  mapM_ (putStrLn . fizzbuzz) [1..100]

fizzbuzz :: Int -> String
fizzbuzz x =
    case (x `mod` 3, x `mod` 5) of
      (0, 0) -> "Fizzbuzz"
      (0, _) -> "Fizz"
      (_, 0) -> "Buzz"
      (_, _) -> show x

So, in DOS:

>ghc fizzbuzz
[1 of 1] Compiling Main             ( fizzbuzz.hs, fizzbuzz.o )
Linking fizzbuzz.exe ...
>fizzbuzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
...
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz

Well, I got that right but only because I've been round this loop a few times before.

No comments:

Post a Comment