FizzBuzz: Simple Program To Show Possibilities Of Tcl edit
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
- The program should be easily understandable
- The program should be easily changeable
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 Implementation edit
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 } } }