Version 8 of SYStems

Updated 2005-11-07 16:55:37

The Egyptian wannabe developer/sys-admin


 # Allow spaces and underscores in long number for easy reading
 proc norm {num} {
     if {[regexp {^(\s|\d|_)*(\.)?(\s|_)*(\d)+(\s|\d|_)*$} $num]} {
         string map {_ {} \  {}} $num
     } else {
         error "Number: $num is not in normal form"
     }
 }

 Example:
 expr [norm 10_000_000] + [norm 100._10_100]
 => 10000100.101

AMG: Neat! Here, this proc does the reverse (adds underscores):

 proc abnorm {num} {
     if {![string is double -strict $num]} {
         error "not a number: \"$num\""
     }
     set point [string first . $num]
     if {$point == -1} {
         set point [string length $num]
     } else {
         for {set pos [expr {$point + 4}]} {$pos < [string length $num]} {incr pos 4} {
             set num [string replace $num $pos $pos _[string index $num $pos]]
         }
     }
     for {set pos [expr {$point - 3}]} {$pos > 0} {incr pos -3} {
         set num [string replace $num $pos $pos _[string index $num $pos]]
     }
     return $num
 }

Example:

 % abnorm 100.10100
 100.101_00
 % abnorm 10000000
 10_000_000
 % abnorm 10000100.101
 10_000_100.101

This code doesn't handle exponential notation or even negative numbers. I don't want to put much more effort into it, since I'm more interested in seeing some clever [regsub] version. If anyone wants to give it a try, ... go right ahead. Have a blast.

AMG: I woke this morning with an inspiration about how to do this with [regsub].

 proc sreverse {str} {
    set result ""
    for {set i [expr {[string length $str] - 1}]} {$i >= 0} {incr i -1} {
      append result [string index $str $i]
    }
    return $result
 }

 proc abnorm {num} {
     if {![string is double -strict $num]} {
         error "not a number: \"$num\""
     }
     regexp {^(-|\+)?(\d+)?(\.)?(\d+)?(e|E)?(-|\+)?(\d+)?$} $num num sm int dot frac e se exp
     set result $sm
     append result [regsub -all {(?!^)(?=(\d{3})+$)} $int _]
     append result $dot
     append result [sreverse [regsub -all {(?!^)(?=(\d{3})+$)} [sreverse $frac] _]]
     append result $e$se$exp
     return $result
 }

I don't bother putting _'s in the exponent because we can't have exponents 1000 and up. I have to do the [sreverse] bit because while we may have look-ahead regular expressions, we do not have look-behind.


RS proposes this simpler version of sreverse:

 proc sreverse str {
    set res ""
    set i [string length $str]
    while {$i} {append res [string index $str [incr i -1]]}
    set res
 }
 % sreverse hello
 olleh

AMG: I saw that on the Additional string functions page, but I chose not to use it because even though it may be more efficient there's a few things I don't like about its style. (This doesn't reflect on your code, merely my preferences.)

  • I feel that it's an abuse to consider 0 to be false and nonzero to be true. I always explicitly make the comparison with zero. Java requires this, by the way. I find this especially important when using [string compare], where a naive reader would assume that ![string compare] tests for string inequality.
  • In many cases I dislike simultaneously using a command's side effect and its return value. Does [incr i -1] return the original value of $i or the changed value? I know the answer, but I would like it to be evident from the code structure.
  • [set res] bothers me because I'm accustomed to seeing [return]. I like being explicit about the fact that a proc returns a value, rather than relying on the (standard, documented) fall-off-the-bottom behavior.
  • I always brace the second argument to [proc] because that reduces the chance of typo if I add arguments or defaults later on.

But I also see that my code's use of [expr] makes it hard to read. [for] is always funny, too. If not for the high price, I might consider using the [unknown] I mention at the bottom of the page of the same name, and write:

 proc sreverse {str} {
     set result ""
     foreach i [[expr {[string length $str] - 1}]-0] {
         append result [string index $str $i]
     }
     return $result
 }

Hmm, that's pretty weird-lookin' too. Now you've got me wishing for $(...) syntax for [expr], but we can never have that because it's already used for arrays named "". $[...] maybe? Ah, I know how to do it without (explicit) [expr]. This is quite nice, I think:

 proc sreverse {str} {
    set result ""
    for {set i 0} {$i < [string length $str]} {incr i} {
      append result [string index $str end-$i]
    }
    return $result
 }

Oh, look what we've done. We hijacked SYStems's page and turned it into a style discussion!


Category Person