Version 8 of Integer range generator

Updated 2004-02-13 16:35:56

if 0 {Richard Suchenwirth 2004-01-25 - For counted loops, Tcl has inherited the for command from C with its lengthy syntax. Python on the other hand provides only a list iterator like Tcl's foreach, and offers an integer range constructor range() to iterate over counted loops:

    range(1,5) -> [1, 2, 3, 4]
    range(4)   -> [0, 1, 2, 3]  

(A similar thing was the index vector generator, iota, in APL). You can also specify the step-width, which might also be negative. This construct comes handy in Tcl too, where we can then choose between

 for {set i 0} {$i < 5} {incr i} {...}
 foreach i [.. 0 5] {...}

I chose the fancy name ".." as suggestive for a range. Here's the code:}

 proc .. {a {b ""} {step 1}} {
    if {$b eq ""} {set b $a; set a 0} ;# argument shift
    if {![string is int $a] || ![string is int $b]} {
        scan $a %c a; scan $b %c b
        incr b $step ;# let character ranges include the last
        set mode %c
    } else {set mode %d}
    set ss [sgn $step]
    if {[sgn [expr {$b - $a}]] == $ss} {
        set res [format $mode $a]
        while {[sgn [expr {$b-$step-$a}]] == $ss} {
            lappend res [format $mode [incr a $step]]
        }
        set res
    } ;# one-armed if: else return empty list
 }

 proc sgn x {expr {$x>0? 1: $x<0? -1: 0}}

if 0 {For testing this, I came up with a cute and tiny asserter/tester routine:}

 proc must {cmd result} {
    if {[set r [uplevel 1 $cmd]] != $result} {
        error "$cmd -> $r, expected $result"
    }
 }

#-- Tests pass silently, but raise an error if expectations are not met:

 must {.. 5}            {0 1 2 3 4}
 must {.. 0 10 3}       {0 3 6 9}
 must {.. -10 -100 -30} {-10 -40 -70}
 must {.. 2 -2 -1}      {2 1 0 -1}
 must {.. 0 0}          {}
 must {.. A D}          {A B C D}
 must {.. z a -1}       {z y x w v u t s r q p o n m l k j i h g f e d c b a}

RWT That's nice, but don't forget that all the power of tcltest is always at your fingertips. And test suites are quite easy. Just like you wrote, but add a test name and description to each one.

 package require tcltest
 namespace import ::tcltest::*
 test range-1.0 {zero to int}          {.. 5}            {0 1 2 3 4}
 test range-1.1 {specify increment}    {.. 0 10 3}       {0 3 6 9}
 test range-1.2 {negatives}            {.. -10 -100 -30} {-10 -40 -70}
 test range-1.3 {negative increment}   {.. 2 -2 -1}      {2 1 0 -1}
 test range-1.4 {zeros}                {.. 0 0}          {}
 test range-2.0 {cap letters}          {.. A D}          {A B C D}
 test range-2.1 {lowercase, backwards} {.. z a -1}       {z y x w v u t s r q p o n m l k j i h g f e d c b a}

RS Hm... On the iPaq, where I test much, I don't have tcltest. And in place of a large thing where the synopsis in the 630-line man page lists just 40 ways of calling, I prefer a 5-liner or less (

 proc must {cmd res} {if {[set r [uplevel 1 $cmd] ne $res} {error "$cmd -> $r, not $res"}}

:-)which I can configure as I wish (e.g. add timing) - and have one dependency less... Nothing against Tcltest, but I usually prefer the simplest thing that works.


KPV It would be useful if this function also had the ability to generate alphabet ranges, e.g [.. A D] => {A B C D}. Perl has this capability and it's surprisingly useful. - RS: Your wish is my command :) It didn't take much to change, considering that characters are integers. For convenience I allowed (as your example suggests) that character ranges run up to the last specified character (while for integers, they run until just below the end limit...)

SS A direct extension is to consider ranges between two words, for example .. z ac 1 => z aa ab. This makes it much more useful, for example you can use it to search a password with brute-force. I don't like the command name .., since there is one very good, descriptive and short: range. Also with the extension I proposed, the command needs an option or to be split in two commands, since e.g. 1 99 is also a valid alphabetical range. - RS: Regarding .., of course you can name it as you wish - I just made that up in fleeting fancy (and to show off that we can have more names than Python ;-) But while characters are integers, words are not (at least in format terms); for this purpose, different code will have to be written.


Arts and crafts of Tcl-Tk programming