FizzBuzz

Fizz Buzz is a programming interview problem and exercise derived from a game meant to teach children arithmetic.

FizzBuzz: Simple Program To Show Possibilities Of Tcl

CecilWesterhof 2018-05-28: I wrote a little program to implement FizzBuzz. Just a simple program to show how easy it is to write a program in tcl and how to split the program in small simple logical parts.

FizzBuzz is a simple problem. You print a range with numbers, with the following rules:

  • When a number can be divided by 3 print Fizz
  • When a number can be divided by 5 print Buzz
  • When a number can be divided by both print FizzBuzz
  • When a number cannot be divided by these values, print the number

I wanted the following extras:

  • The program should be easily understandable
  • The program should be easily changeable

For example it should be easy to change the values to divide by, or the text used with a value.

The main part

init
for {set i $startVal} {$i <= $endVal} {incr i} {
    puts [transformNumber $i]
}

Initialise the program and then print the possible transformed numbers.

Transforming the number

proc transformNumber {number} {
    global fizzBuzz

    set output ""
    dict for {value string} $fizzBuzz {
        if {$number % $value == 0} {
            append output $string
        }
    }
    if {$output == ""} {
        set output $number
    }
    return $output
}

For all values to use append the string to use when necessary. If the number is transformed return the transformation, otherwise return the number.

Initialising

proc init {} {
    global endVal
    global fizzBuzz
    global startVal

    set fizzBuzz [dict create   3 Fizz   5 Buzz]
    set startVal    1
    set endVal      100

    readParams
    if {![string is integer -strict $startVal]} {
        giveError "startValue is not an integer"
    }
    if {![string is integer -strict $endVal]} {
        giveError "endValue is not an integer"
    }
    if {$endVal < $startVal} {
        giveError "endValue < startValue"
    }
}

Create the list with transformations and initialise the start and end value. Read the parameters. After this check the values. By checking the values in init and not readParams the default values are also checked.

Reading the parameters

proc readParams {} {
    global argc
    global argv
    global endVal
    global startVal

    if {($argc == 1) && ([lindex $argv 0 ] == "--help")} {
        puts [usageStr]
        exit
    }
    switch $argc {
        0 {}
        1 {
            set endVal   [lindex $argv 0]
        }
        2 {
            set startVal [lindex $argv 0]
            set endVal   [lindex $argv 1]
        }
        default {
            giveError "WRONG ARGUMENTS\n[usageStr]"
        }
    }
}

If the parameter is --help show the usage of the script. If there are no parameters use default values. If there is one parameter this is the end value. If there are two parameters these are the start and end values. Otherwise there is an error, signal this, give the usage and exit.

Usage

proc usageStr {} {
    set command [file tail $::argv0]

    return "USAGE:
    $command
    $command endValue
    $command startValue endValue
    $command --help

Default: startValue = $::startVal, endValue = $::endVal"
}

Just returns a string with the usage of the script.

Handling errors

proc giveError {message {error 1}} {
    giveWarning $message
    exit $error
}

proc giveWarning {message} {
    puts stderr $message
}

An error is a warning that exits the program. In this case warnings are not given, but I prefer to be prepared for this kind of situations. In my opinion errors and warnings should go to the same place. In this way there is not the risk that when warnings are added that they not go to the same place as the errors.


As always: comments, tips and questions are appreciated.


The complete program

#!/usr/bin/env tclsh


proc giveError {message {error 1}} {
    giveWarning $message
    exit $error
}

proc giveWarning {message} {
    puts stderr $message
}

proc init {} {
    global endVal
    global fizzBuzz
    global startVal

    set fizzBuzz [dict create   3 Fizz   5 Buzz]
    set startVal    1
    set endVal      100

    readParams
    if {![string is integer -strict $startVal]} {
        giveError "startValue is not an integer"
    }
    if {![string is integer -strict $endVal]} {
        giveError "endValue is not an integer"
    }
    if {$endVal < $startVal} {
        giveError "endValue < startValue"
    }
}

proc readParams {} {
    global argc
    global argv
    global endVal
    global startVal

    if {($argc == 1) && ([lindex $argv 0 ] == "--help")} {
        puts [usageStr]
        exit
    }
    switch $argc {
        0 {}
        1 {
            set endVal   [lindex $argv 0]
        }
        2 {
            set startVal [lindex $argv 0]
            set endVal   [lindex $argv 1]
        }
        default {
            giveError "WRONG ARGUMENTS\n[usageStr]"
        }
    }
}

proc transformNumber {number} {
    global fizzBuzz

    set output ""
    dict for {value string} $fizzBuzz {
        if {$number % $value == 0} {
            append output $string
        }
    }
    if {$output eq ""} {
        set output $number
    }
    return $output
}

proc usageStr {} {
    set command [file tail $::argv0]

    return "USAGE:
    $command
    $command endValue
    $command startValue endValue
    $command --help

Default: startValue = $::startVal, endValue = $::endVal"
}


init
for {set i $startVal} {$i <= $endVal} {incr i} {
    puts [transformNumber $i]
}

Alternate Implementations

dbohdan 2018-04-01: The canonical way to solve Fizz Buzz in Tcl is this:

for {set i 1} {$i <= 100} {incr i} {
    switch -glob [expr {$i % 3}]_[expr {$i % 5}] {
        0_0 { puts {Fizz Buzz} }
        0_* { puts Fizz }
        *_0 { puts Buzz }
        *_* { puts $i }
    }
}

sebres 2019-07-31: as single-liner using dict getdef (tcl >= 8.7):

for {set i 1} {$i <= 100} {incr i} {
    puts [dict getdef {0 "Fizz Buzz" 1 Fizz 2 Buzz} [expr {bool($i % 3)<<1 | bool($i % 5)}] $i]
}

Discussion