Posted to tcl by patthoyts at Fri Jun 12 13:30:31 GMT 2009view pretty

# Recursive functions are a bad thing if there is a lot of recursion
# to be done. Tail recursion is the way to speed up recursion and can
# sometimes express things more simply than iterative approaches while
# still being as fast as iteration.
# 
# For instance the following 3 versions of the fibonacci function use
# iterative, recursive or tail-recursive approaches. Lets see:
#
# C:\opt\tcl\src>tclkitsh86 fibonacci.tcl -estimate 100
# 354224848179263111168 32.6
# 
# C:\opt\tcl\src>tclkitsh86 fibonacci.tcl -iterative 100
# 354224848179261915075 153.0
# 
# C:\opt\tcl\src>tclkitsh86 fibonacci.tcl -tailcall 100
# 354224848179261915075 550.0
# 
# C:\opt\tcl\src>tclkitsh86 fibonacci.tcl -memoized 100
# 354224848179261915075 955.2
# 
# NOTE that I didn't dare to try -rec 100 as it is too slow and will run out
#      of stack for n=100.

# Tail-recursive
proc fibT {n} {
    proc fib' {a b n} {
        if {$n < 1} {
            return $a
        } elseif {$n == 1} {
            return $b
        } else {
            tailcall fib' $b [expr {$a + $b}] [expr {$n - 1}]
        }
    }
    return [fib' 0 1 $n]
}

# Fully recursive
proc fibR {n} {
    if {$n < 3} then {
        expr 1
    } else {
        expr {[fibR [expr {$n-1}]]+[fibR [expr {$n-2}]]}
    }
}

# Iterative
proc fibI n {
    if {$n < 2} {return $n}
    set prev 1
    set fib 1
    for {set i 2} {$i < $n} {incr i} {
        lassign [list $fib [incr fib $prev]] prev fib
    }
    return $fib
}

# Estimated version - good up to about 70
proc fibE {n} {expr {round((.5 + .5*sqrt(5)) ** $n / sqrt(5))}}

# Memoized - ie: cache the calculated numbers
# RS version...
proc memoize {} {
    global memo
    set cmd [info level -1]
    if {[info level] > 2 && [lindex [info level -2] 0] eq "memoize"} return
    if { ! [info exists memo($cmd)]} {set memo($cmd) [eval $cmd]}
    return -code return $memo($cmd)
}
proc fibM {n} {
    memoize
    expr {$n < 3 ? 1 : [fibM [expr {$n - 1}]] + [fibM [expr {$n - 2}]]}
}

# Now NEM's version ...
proc xmemoize f {
    variable $f [dict create]
    rename $f _cache_$f
    interp alias {} $f {} xremember $f
}
proc xremember {f args} {
    upvar #0 $f cache
    if {![dict exists $cache $args]} {
        dict set cache $args [uplevel 1 [linsert $args 0 _cache_$f]]
    }
    dict get $cache $args
}
proc func {name params body} { proc $name $params [list expr $body] }
func fibN n {
    $n < 2 ? $n
           : [fibN [expr {$n-1}]] +
             [fibN [expr {$n-2}]]
}
xmemoize fibN


if {!$tcl_interactive} {
    set cmd fibT
    if {[llength $argv] > 1} {
        switch -glob -- [lindex $argv 0] {
            "-t*" { set cmd fibT }
            "-i*" { set cmd fibI }
            "-r*" { set cmd fibR }
            "-e*" { set cmd fibE }
            "-m*" { set cmd fibM }
            "-n*" { set cmd fibN }
            default {
                puts "usage: fib ?-tail-recursive? ?-estimated?\
                   ?-iterative? ?-recursive? ?-memoized? number"
            }
        }
        set argv [lrange $argv 1 end]
    }
    set t [time {set r [catch [linsert $argv 0 $cmd] err]} 5]
    if {$r} {puts $errorInfo} else {puts "$err [lindex $t 0]"}
    exit $r
}