Tcl error in doing sum - tcl

I want to compute average on a list of numbers stored in a tcl list P. Here's my script :
set sum 0.0
foreach e $P { set sum [expr {$sum + $e}] }
set avg [expr {1.0*$sum / [llength $P]}]
But I have the error : can't use non-numeric string as operand of "+"
How can I do the sum ?

Your problem is probably due to some element in P not being a number. In any case, this is how you calculate average:
package require math::statistics
::math::statistics::mean $P
assuming P is a list of numbers.
If you have a list of data items and want to know if any of them are unsuitable for expr arithmetic you can do something like this:
foreach n $data {
if {![string is double -strict $n]} {
error "$n is not a number"
}
}
This will report the first non-number. The string is double command recognizes both integers and floating point numbers1. If you leave out the -strict flag, the empty string will be considered a number (expr will still choke on it, though2).
This will give you the sublist of all non-number items in $data:
lmap n $data {
if {![string is double -strict $n]} {set n} continue
}
And this will give you the sublist of all proper-number items in $data:
lmap n $data {
if {[string is double -strict $n]} {set n} continue
}
1 the name "double" indicates that it returns true for any string that can be translated to the C data type double, which refers specifically to storage of a double precision floating point number, (an industry standard for encoding floating-point numbers). If you don't know what that is, you can pretend that it means double as in "both numbers that look like integers and numbers that look like reals".
2 expr will also choke on the value NaN which is a perfectly valid floating point value, it just stands for "not a number".
Documentation: continue, error, expr, foreach, if, lmap, math::statistics package, package, set, string

Related

How to compare tcl strings with don't care chars in the middle?

I have lists of strings I want to compare
When comparing 2 strings, I want to ignore a single char - making it a don't care.
e.g.
Mister_T_had4_beers
should be equal to:
Mister_Q_had4_beers
but shouldn't be equal to Mister_T_had2_beers
I know that _had\d+ will always appear in the string, so it can be used as an anchor.
I believe I can split the 2 strings using regexp and compare, or use string equal -length to the point and from it onwards, but there must be a nicer way...
Edit
Based on the answer below (must read - pure gold!) the solution comes from regexp:
regexp -line {(.*).(_had\d+.*)\n\1.\2$} $str1\n$str2
If you know which character can vary, the easiest way is to use string match with a ? at the varying position.
if {[string match Mister_?_had4_beers $string1]} {
puts "$string1 matches the pattern"
}
You can also use string range or string replace to get strings to compare:
# Compare substrings; prefixes can be done with [string equal -length] too
if {[string range $string1 0 6] eq [string range $string2 0 6]
&& [string range $string1 8 end] eq [string range $string2 8 end]} {
puts "$string1 and $string2 are equal after ignoring the chars at index 7"
}
# Compare strings with variation point removed
if {[string replace $string1 7 7] eq [string replace $string2 7 7]} {
puts "$string1 and $string2 are equal after ignoring the chars at index 7"
}
To have the varying point be at an arbitrary position is trickier. The easiest approach for that is to select a character that is present in neither string, say a newline, and use that to make a single string that we can run a more elaborate RE against:
regexp -line {^(.*).(.*)\n\1.\2$} $string1\n$string2
The advantage of using a newline is that regexp's -line matching mode makes . not match a newline; we need to match it explicitly (which is great for our purposes).
If the strings you're comparing have newlines in, you'll need to pick something else (and the preferred RE gets more long-winded). There's lots of rare Unicode characters you could choose, but \u0000 (NUL) is one of the best as it is exceptionally rare in non-binary data.

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)"
}

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 *?[]\)

Why does Tcl expr command returns integers by default?

Why does Tcl expr command returns integers by default? I have read the documentation (Section "Types, overflow and precision"), but is there any other way to "make" expr to return float except adding 0.0 to the result and similar?
For example, I'm learning Tcl and I made a simple program to calculate average value:
puts -nonewline stdout "Please enter scores: "
flush stdout;
set score [gets stdin];
set sum 0;
set counter 0;
foreach mark $score {
set sum [expr {$sum + $mark}];
incr counter;
}
puts "Your average score is: [expr {$sum/$counter}]";
As an example:
Please enter scores: 1 2 4
Your average score is: 2
Otherwise:
Please enter scores: 1 2 4.0
Your average score is: 2.333333333333335
Is there other way for expr to return float, or do I have to insert 0.0, or *.0 here and there to make sure I get result I want? And why is that so?
If any of the numbers in the expression are floats, it will return a float. From the expr man page:
For arithmetic computations, integers are used until some floating-point number is introduced, after which floating-point is used.
So, you can do something like this:
puts "Your average score is: [expr {double($sum)/$counter}]";
Another possibility is to make sure that sum or mark is already a double:
set sum 0.0
...
set sum [expr {$sum + $mark}]

Is there an equivalent in Tcl of 'string to X' functions found in C stdlib.h?

There are standard functions such as atof and atoi in C's stdlib.h for converting strings to floats / integers (and to do the reverse too). Is there an equivalent of this in Tcl or do I need to write my own process for carrying out these tasks?
Everything is a string in Tcl, but functions that expect a number (like expr) will use that 'string' as an integer:
% set str " 123 "
123
% set num [expr $str*2]
246
If you want to format a number in a specific way (like producing a floating a point number of a specific precision) then you can use format:
% set str " 1.234 "
1.234
% set fnum [format "%.2f" $str]
1.23
As noted, everything is a string in Tcl, so you can just use a given string as an integer or whatever else you need it as. The only caveat being that it needs to be something that can be interpreted as what you want to use it as (ie, you can use "a" as an integer)
You can test to see if something can be interpreted as the type you want using the string is subcommand:
string is integer "5" ;# true
string is integer "a" ;# false
string is list "a b cc" ;# true
string is list "{a b}c" ;# false
I should note as well that equivatents to atof and atoi can be viewed as conversion of internal Tcl data structures to external binary representations. This is done by the [binary format] command.
One can test string is double $x before using $x in expressions.
E.g., [string is double 1.2.3] returns 0
In my case, this code worked:
set a [string trimleft $a 0]