Comparison of two alphanumeric values in tcl - tcl

Can anyone help me in comparing two alphanumeric values in tcl.
If say I have two values of versions
set val1 "2.1.a"
set val2 "1.2.a"
And if I want to get the max of two values i.e. $val1 ( as per above example), how can I do that?
Is there any way besides doing character by character comparison?
Possible set of values:
1.0
1.1a
1.2.3f
2.1
Thanks in advance.

try doing as follows:
set val1 "2.1.a"
puts $val1
set val2 "1.2.a"
puts $val2
puts [string compare $val2 $val1]
It performs a character-by-character comparison of strings string1 and string2. Returns -1, 0, or 1, depending on whether string1 is lexicographically less than, equal to, or greater than string2
Go through this link for further details;
hope it will solve your problem.

I would break up the version string into a list, compare them one by one:
# Breaks the version string into a list of tokens
proc tokenize {v} {
set v [string map { " " "" } $v]
set result {}
foreach token [split $v ".-"] {
set tokens_scanned [scan $token "%d%s" number alpha]
if {$tokens_scanned == 0} {lappend result $token} ;# is alpha, e.g. beta
if {$tokens_scanned == 1} {lappend result $number} ;# is number, e.g. 5
if {$tokens_scanned == 2} {lappend result $number $alpha} ;# is both, e.g. 5beta
}
return $result
}
# Examples of versions this function handles:
# 1, 1a, 1.5, 3.2b, 3.2.1, 3.2.1-beta1
proc compare_version {v1 v2} {
# Sanitize the data
set v1 [tokenize $v1]
set v2 [tokenize $v2]
foreach token1 $v1 token2 $v2 {
if {$token1 < $token2} { return -1 }
if {$token1 > $token2} { return 1 }
}
return 0
}

By Tcl documentation, to compare the two version numbers in tcl you should use package command:
package vcompare version1 version2
Compares the two version numbers given by version1 and version2. Returns -1 if version1 is an earlier version than version2, 0 if they are equal, and 1 if version1 is later than version2.

Related

Numeric Value Comparison in Tcl

I want to know how to get a numeric value in TCL. What I mean is that, if the value is not numeric, result should fail else pass.
The below is what I have tried;
set trueKIND false
set trueKINDlist [list 1 2 3 4 5 6 7 8 9 10]
if {[lsearch -exact $trueKINDlist $Registrant(KIND)] >= 0} {
set trueKIND true
}
But what happens if the value of trueKINDlist > 10, this code certainly will fail...
Can somebody please tell me how I can write this in TCL? Or assist me with the operator to use in achieving this...
Thanks
Mattie
You can validate the string by [string is ...] procedure. For example:
set trueKIND [string is integer -strict $Registrant(KIND)]
Reference: https://www.tcl.tk/man/tcl8.6/TclCmd/string.htm#M10
You've got to think what kind of validation you want. For example, if you want to just validate that the value is an integer, any integer, do this:
if {![string is entier -strict $value]} {
error "$value is not an integer"
}
(There is also string is integer, but that uses a restricted 32-bit range for historical reasons, and string is wide uses a 64-bit range. For floating point numbers, use string is double. The -strict is required here; without it the empty string is also accepted; again, this is for historical reasons.)
When you have a particular range you want the value to be in, you use a compound condition:
if {![string is entier -strict $value] || !($value >= 0 && $value <= 10)} {
error "$value is not an integer in the range (0..10)"
}
If you are doing this a lot, use a procedure to make it clearer:
proc IntegerInRange {value lowerBound upperBound} {
expr {[string is entier -strict $value] && $value >= $lowerBound && $value <= $upperBound}
}
if {![IntegerInRange $value 0 10]} {
error "$value is not an integer in the range (0..10)"
}
if {![IntegerInRange $value2 3 25]} {
error "$value2 is not an integer in the range (3..25)"
}

Addition or Subtraction of a number from each element of a list using Tcl Script

I have a input file name "input.dat" with the values as:
7 0
9 9
0 2
2 1
3 4
4 6
5 7
5 6
And I want to add/subtract any number from column 2 by converting it into a list using Tcl Script. I have written the Tcl Script as follows:
set input [open "input.dat" r]
set data [read $input]
set values [list]
foreach line [split $data \n] {
if {$line eq ""} {break}
lappend values [lindex [split $line " "] 1]
}
puts "$values-2"
close $input
But the output comes out to be: 0 9 2 1 4 6 7 6-2
Can anybody help me, how to fix this problem ? or what is the error in the script ? It's also helpful if anybody can help me with a correct script.
I'm still not 100% sure what you want, but the options all seem to be solvable with the lmap command, which is for applying an operation to each element of a list.
Here's how to concatenate each element with -2:
set values [lmap val $values {
string cat $val "-2"
}]
Here's how to subtract 2 from each element:
set values [lmap val $values {
expr {$val - 2}
}]
puts will treat it as a string, you'll have to use [expr $val - 2]
NOTE: If it doesn't work, it is possible your input list is a string not int or float (Depends on how the values were read). In this case you can use:
scan $val %d tmp
set newval [expr $tmp - 2]
puts $newval
This will convert your string to int before applying mathematical expressions. You can similarly convert to float by using %f in scan instead of %d

TCL coming up with a general code

I am using tcl. Below is the code I have so far; is there a better way to do this?
if {$parname == "Wbay1" || $parname == "Wbay2" } {
set count [string index $parname end]
set Wbay$count [lindex $elem 1]
puts "set Wbay$count [lindex $elem 1]"
}
Be more general like this
if {$parname == "Wbay*" } {
set count [string index $parname end]
set Wbay$count [lindex $elem 1]
puts "set Wbay$count [lindex $elem 1]"
}
If the names are Wbay1, ..., Wbay9, you can use
if {[string match {Wbay[1-9]} $parname]} {
set $parname [lindex $elem 1]
}
If the number part can be greater than 9, you should use
if {[regexp {Wbay\d+} $parname]} {
set $parname [lindex $elem 1]
}
A regexp (regular expression) match is more powerful than a string match. In this case, \d+ means "one or more digits".
If you want to record the highest number you've seen so far, use
set maxN 0
...
if {[regexp {Wbay(\d+)} $parname -> n]} {
set maxN [expr {max($n, $maxN)}]
set $parname [lindex $elem 1]
}
The parenthesis means that you want to capture the matched string within, i.e. the number. The -> symbol is a variable name: it's a convention that is often used to store the full match (e.g. "Wbay7") when we don't care about it. The variable n is set to the number that was captured. If regexp doesn't return 1, the value in n can't be trusted: the variable will keep whatever value it had before. The variable maxN is set to whichever of $n and $maxN is greatest.
But you might also find an array variable useful. With an array, you name the individual members Wbay(1), Wbay(2), Wbay(99), etc (they don't have to be consecutive or in order). If you want to know how many members you have, array size Wbay will tell you.
Documentation:
array,
expr,
if,
lindex,
max (function),
regexp,
set,
string,
Syntax of Tcl regular expressions
Syntax of Tcl string matching:
* matches a sequence of zero or more characters
? matches a single character
[chars] matches a single character in the set given by chars (^ does not negate; a range can be given as a-z)
\x matches the character x, even if that character is special (one of *?[]\)

How to find ',' in a string in TCL

I am new to TCL, just wanted to know that how can we search for "," in a string and want the particular string before and after.
Example : tampa,florida
It has to search for , if in that string if there is , it should return tampa and florida we can use string replace but it will not work in my condition because i need to map, tampa and florida to different set of variables dont even know how the inbound would look like to use string range.
.
Thanks,
Arya
Unless there is some further condition, you could do it this way:
split tampa,florida ,
This command gives as result a list containing the two strings "tampa" and "florida".
Documentation: split
The shortest piece of code to do this would be using regular expressions:
if {[regexp {(.+),(.+)} $string a b c]} {
# $a is the complete match. But we don't care
# about that so we ignore it
puts $b; #tampa
puts $c; #florida
}
The regular expression (.+),(.+) means:
(
. any character
+ one or more of the above
) save it in a capture group
, comma character
(
. any character
+ one or more of the above
) save it in a capture group
See the documentation of regular expression syntax in tcl for more about regular expressions: https://www.tcl.tk/man/tcl8.6/TclCmd/re_syntax.htm
But if you're not familiar with regular expressions and want an alternative way of doing this you can use the various string commands. This is one way to do it:
set comma_location [string first "," $string]
if {$comma_location > -1} {
set a [string range $string 0 [expr {$comma_location -1}]
set b [string range $string [expr {$comma_location +1}] end]
puts $a; #tampa
puts $b; #florida
}
A variant of slebetman's last answer.
proc before_after {value find {start 0}} {
set index [string first $find $value $start]
set left_side [string range $value $start [expr $index - 1]]
set right_side [string range $value [expr $index + 1] end]
return [list $left_side $right_side]
}
puts [before_after "tampa,fl" ","]
output:
tampa fl

TCL incr gives wrong value for zero padded integer

I was trying to increment a number which is padded by zeroes to become a six digit number. But strangely any value other than single digit gives a wrong value. like
set x 000660
incr x 1
gives result 433. Also tried with smaller number like 010 but the result is 9. Why is this happening ?
What is the proper way to solve this issue ?
You can try this way too.
proc getIntVal { x } {
# Using 'scan' command to get the literal integer value
set count [ scan $x %d n ]
if { $count!= 1 } {
return -1
}
return $n
}
proc padZero { x } {
# Using 'format' to pad with leading zeroes.
return [ format "%05d" $x ]
}
set val 00060
puts "Initial value : $val"
set tmp [ getIntVal $val ]; # 'tmp' will have the value as '60'
incr tmp;
set val [ padZero $tmp ]; # Padding with zero now
puts "Final value : $val"
Numbers beginning with 0 like
000660
are octet integers. It's equivalent to decimal 432.
The same for 010 (the same as 8 in decimal)
To strip off zeros, try this:
proc stripzeros {value} {
regsub ^0+(.+) $value \\1 retval
return $retval
}
For more information, see Tcl FAQ: How can I use numbers with leading zeroes?.
Yu Hao already explained the problem of octets, and Dinesh added some procs to circumvent the issue. I am suggesting creating one proc that will take on a zero padded integer and return another zero padded integer of the same format and which should work just like incr:
proc incr_pad {val args} {
# Check if increment is given properly
if {[llength $args] == 0} {
set args 1
} elseif {[llength $args] > 1} {
return -code error {wrong # args: should be "incr_pad varName ?increment?"}
}
# Check for integers
if {![regexp {^[0-9]+$} $val]} {
return -code error "expected integer but got \"$val\""
} elseif {![regexp {^[0-9]+$} $args]} {
return -code error "expected integer but got \"$args\""
}
# Get number of digits
set d [regexp -all {[0-9]} $val]
# Trim 0s to the left
set newval [string trimleft $val 0]
# Now use incr
incr newval $args
# Return back the number formatted with the same zero padding as initially given
return [format "%0${d}d" $newval]
}
With this...
% incr_pad 000660 1
000661
% incr_pad 2.5 1
expected integer but got "2.5"
% incr_pad 02 1.5
expected integer but got "1.5"
% incr_pad 010 2
012
% incr_pad 1 2 3
wrong # args: should be "incr_pad varName ?increment?"
% incr_pad 00024
00025
% incr_pad 999
1000
Of course, you can change the name of the function to a shorter one or one which you find more appropriate.