Version 6 of FizzBuzz

Updated 2018-05-29 16:17:20 by dbohdan

Created by CecilWesterhof.

See also

FizzBuzz: Simple Program To Show Possibilities Of Tcl

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 fizzBuzzLst

    set output ""
    foreach {value string} ${fizzBuzzLst} {
        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 fizzBuzzLst
    global startVal

    set fizzBuzzLst {
        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 fizzBuzzLst
    global startVal

    set fizzBuzzLst {
        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 fizzBuzzLst

    set output ""
    foreach {value string} ${fizzBuzzLst} {
        if {${number} % ${value} == 0} {
            append output ${string}
        }
    }
    if {${output} == ""} {
        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}]
}

Discussion