Problem solving

This unit is an addendum to the problem solving unit of The Odin Project. Please read through said unit till you reach the section "Solving Fizz Buzz". This unit will pick up from there.

If you have any issues understanding the material in this unit, please ask questions in the discord channel I've set up for teaching Scala, or you can discuss on scala-users.

Please remember to run the provided examples via the Scala console unless told otherwise. If you've forgotten how to launch and/or shut down the Scala console, please refer here.

Solving Fizz Buzz

Just like in the Odin Project material, we will demonstrate the problem solving workflow by solving a common programming exercise: FizzBuzz.

We will be solving the problem with a proper Scala program, not the console. For this exercise, you'll be writing the solution in a file named FizzBuzz.scala. Please be sure to create one in your text editor.

Understanding The Problem

Write a program that takes a user's input and prints the numbers from one to the number the user entered. However, for multiples of three print Fizz instead of the number and for the multiples of five print Buzz. For numbers which are multiples of both three and five print FizzBuzz.

This is the big picture problem we will be solving. It's pretty simple so we may not need to reword it. However, we can simplify it by rewording it a bit.

For our program, the user should be able to enter a number. When the program receives this number, which we'll call the limit, it should examine each number between one and the limit. For numbers that are wholly divisible by three, print Fizz. For numbers that are wholly divisible by five, print Buzz. If a number is divisible by both three and five, print FizzBuzz. Finally, for numbers that are not wholly divisible by three or five, print said number.

Plan

Does the program have an interface? What will it look like? Our FizzBuzz solution will be a console program, so the interface will be input provided at the start of the program. There won't be any user interaction otherwise.

What inputs will your program have? Will the user enter data or will you get input from somewhere else Our FizzBuzz solution will have inputs to its entrypoint.

What's the desired output? The desired output will be printed numbers, and the Strings "Fizz", "Buzz", and "FizzBuzz". Example output for the input 15 is:

1
2
Fizz
4
Buzz
6
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Given your inputs, what are the steps necessary to return the desired output? The algorithm in pseudocode for this problem:

When the program starts, it will receive a number
Create a List from 1 to the received number
Process the list, and for each element:
  if the element is divisible by 3 then print "Fizz"
  if the element is divisible by 5 then print "Buzz"
  if the element is divisible by both then print "FizzBuzz"
  otherwise print the element

Divide and Conquer (Implement)

As we can see from the algorithm we developed, the first subproblem is receiving a number at the start of the program. We can do this with an Entrypoint, the method a program starts from. Let's start there and verify we've solved it by printing the entered number.

A proper Scala program must always start from an EntryPoint. You may have seen one when we had previous sections that used a proper Scala program with the scala-cli run command. In any case, the syntax for an entrypoint is like a normal method, but with a @main in front of def.

Please copy the following code into FizzBuzz.scala and run scala-cli run FizzBuzz.scala.

@main def demoEntryPoint = println("I'm a demo")

By putting @main in front of a method, you tell Scala that your program starts with that method.

In order to get input on program start, we add @main to a method that has parameters. If you remember, we want a limit as input, and this limit should be a whole number. So the entrypoint for our FizzBuzz program should look like:

@main def fizzBuzz(limit: Int) = println(limit)

Please copy this line into your FizzBuzz.scala and delete the line with demoEntryPoint.

If you try scala-cli run FizzBuzz.scala now, you'll get an error.

Illegal command line: more arguments expected

This error indicates that the program expected input, and you didn't give it enough. To give it input, you can now use this modified run command: scala-cli run FizzBuzz.scala -- 4. Here, 4 is our limit input to the program. When running this command, you should now get 4 back as a response.

We now have input to our program, solving step 1 of our pseudocode. Let's move on to the next subproblem: "Create a List from 1 to the received number". There are many ways to do this in Scala, but if you read the last unit, you should already know of one: List.range.

@main def fizzBuzz(limit: Int) = 
  val list = List.range(1, limit+1)
  println(list)

If you re-run your program with scala-cli run FizzBuzz.scala -- 15 you should get the output List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15). With that working, lets move on to the next problem: processing the list.

We'll do this step with some code that outputs the number we're on at the moment:

@main def fizzBuzz(limit: Int) = 
  val list = List.range(1, limit+1)
  for number <- list
  yield println(number)

Now we have each number in the list being printed out. Next we need our for-expression to do different things depending on what the current element is.

Since this will end up being more complex than anything else in our program, we will separate it out into its own method. Lets call this method response, and it will print Fizz if the current number is divisible by 3:

@main def fizzBuzz(limit: Int) = 
  val list = List.range(1, limit+1)
  for number <- list 
  yield response(number)

def response(number: Int) = 
   if number % 3 == 0 then 
      println("Fizz")
   else 
      println(number)

Separating out this code from the main fizzBuzz entrypoint means that we can test it on its own using the console. Load your program code into the console with scala-cli console FizzBuzz.scala, and you can test how your response method is working:

scala> response(3)
Fizz

scala> response(5)
5

scala> response(2)
2

So far, so good. Next, we'll check if the number is divisible by 5, and print Buzz if so:

@main def fizzBuzz(limit: Int) = 
  val list = List.range(1, limit+1)
  for number <- list 
  yield response(number)

def response(number: Int) = 
   if number % 3 == 0 then 
      println("Fizz")
   else if number % 5 == 0 then
      println("Buzz")
   else 
      println(number)

If we test this again with the console, we should see this new behavior in our response method:

scala> response(5)
Buzz

scala> response(2)
2

scala> response(3)
Fizz

Finally, we check if the element is divisible by both 3 and 5.

@main def fizzBuzz(limit: Int) = 
  val list = List.range(1, limit+1)
  for number <- list 
  yield response(number)

def response(number: Int) = 
   if number % 3 == 0 && number % 5 == 0 then
      println("FizzBuzz")
   else if number % 3 == 0 then 
      println("Fizz")
   else if number % 5 == 0 then
      println("Buzz")
   else 
      println(number)

You'll notice that the case for when number is divisible by 3 was moved below the one that checks if the number is divisible by 3 and by 5. That's because if we left 3 at the top, it would be impossible to reach the more specific case where the number is divisible by both 3 and 5; the 3 case would always be run instead. You must remember to order your if-expressions in order of specificity: most specific up top, least specific at the bottom.

Testing our response method one more time in the console suggests we have the right code:

scala> response(3)
Fizz

scala> response(5)
Buzz

scala> response(15)
FizzBuzz

scala> response(2)
2

Now lets test the full program again. Run the following command to test the numbers 1-20: scala-cli run FizzBuzz.scala -- 20

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

At this point, please return to Assignment section of The Odin Project page. Do the assignment, and join us for the next unit!

Did you find this article valuable?

Support Mark Hammons by becoming a sponsor. Any amount is appreciated!