Tcl: strange behavior of string list - string

In my following code, I generate a string list in a for loop like this:
set section 5;
set value 2;
set value_range_new "";
for { set i [expr -$section-1]} {$i <= $section} {incr i} {
if {$i < [expr -$section]} {
lappend value_range_new "\<[expr [expr $i+1]*$value]";
} elseif {$i == $section} {
lappend value_range_new "\>[expr $i*$value]";
} else {
lappend value_range_new "\[[expr $i*$value]\,[expr [expr $i +1]*$value]\)";
}
}
then if I puts the list out, the result is following:
<-10 {[-10,-8)} {[-8,-6)} {[-6,-4)} {[-4,-2)} {[-2,0)} {[0,2)} {[2,4)} {[4,6)} {[6,8)} {[8,10)} >10
The confusing point is I do not understand where the {} comes from. If I define the list manually like following :
set a "\<-8 \[-8,-6\) \[-6,-4\) ";
the puts result has no {}. So what's wrong with my code, and how to remove/

In Tcl, you need to be aware which commands want to work with lists and which commands want to work with strings. lappend is a list command. puts takes a string. When you give puts a list, Tcl will convert the list into its string representation. That means that you'll see some extra braces and perhaps backslashes to protect list elements that have special characters (like [ and ]).
You can convert the list into a string easily with the join command.
For building strings, the format command can aid readability.
Additionally, you don't need to next expr commands, use parentheses; and brace your expressions:
set section 5;
set value 2;
set value_range_new "";
for { set i [expr {-$section-1}]} {$i <= $section} {incr i} {
set this [expr {$i * $value}]
set next [expr {($i + 1) * $value}]
if {$i < -$section} {
lappend value_range_new [format {<%d} $next]
} elseif {$i == $section} {
lappend value_range_new [format {>%d} $this]
} else {
lappend value_range_new [format {[%d,%d)} $this $next]
}
}
puts [join $value_range_new]
outputs
<-10 [-10,-8) [-8,-6) [-6,-4) [-4,-2) [-2,0) [0,2) [2,4) [4,6) [6,8) [8,10) >10

The shorter variant of algorithm. Not a single if is needed actually.
set section 5;
set value 2;
set this [expr {-$section * $value}]
set value_range_new [format {<%d} $this]
for {set i -$section} {$i < $section} {incr i} {
lappend value_range_new [format {[%d,%d)} $this [incr this $value]]
}
lappend value_range_new [format {>%d} $this]
puts [join $value_range_new]

Related

Proper ways to use threads in TCL

So I have the following code -
#!/usr/bin/env tclsh
package require Thread
thread::create {
for {set i 0} {$i < 1000} {incr i} {
puts "hello T1 $i"
}
thread::wait
}
thread::create {
for {set j 0} {$j < 1000} {incr j} {
puts "hello T2 $j"
}
thread::wait
}
It runs, but the first thread run more iterations than the loop count (1000) and the second thread has far fewer iterations than its loop count (1000 ). Can someone point out what's wrong with this code? Many thanks for your help.

How to move the decimal point N places to the left efficiently?

I have a bunch of decimal numbers (as strings) which I receive from an API. I need to 'unscale' them, i.e. divide them by some power of 10. This seems a simple task for integers, but I have decimals with no guaranteed range. So, basically I need a function that works like this:
move_point "12.34" 1; # "1.234"
move_point "12.34" 5; # "0.0001234"
I'd rather not use floats to avoid any rounding errors.
This is a bit verbose, but should do the trick:
sub move_point {
my ($n, $places) = #_;
die 'negative number of places' if $places < 0;
return $n if $places == 0;
my ($i, $f) = split /\./, $n; # split to integer/fractional parts
$places += length($f);
$n = sprintf "%0*s", $places+1, $i.$f; # left pad with enough zeroes
substr($n, -$places, 0, '.'); # insert the decimal point
return $n;
}
Demo:
my $n = "12.34";
for my $p (0..5) {
printf "%d %s\n", $p, move_point($n, $p);
}
0 12.34
1 1.234
2 0.1234
3 0.01234
4 0.001234
5 0.0001234
Unless your data has contains values with significantly more digits than you have shown then a floating-point value has more than enough accuracy for your purpose. Perl can reliably reproduce up to 16-digit values
use strict;
use warnings 'all';
use feature 'say';
say move_point("12.34", 1); # "1.234"
say move_point("12.34", 5); # "0.0001234"
say move_point("1234", 12);
say move_point("123400", -9);
sub move_point {
my ($v, $n) = #_;
my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
$dp += $n;
$v /= 10**$n;
sprintf '%.*f', $dp < 0 ? 0 : $dp, $v;
}
output
1.234
0.0001234
0.000000001234
123400000000000
Update
If the limits of standard floating-point numbers are actually insuffcient for you then the core Math::BigFloat will do what you need
This program shows a number with sixteen digits of accuracy, multiplied by everything from 10E-20 to 10E20
use strict;
use warnings 'all';
use feature 'say';
use Math::BigFloat;
for ( -20 .. 20 ) {
say move_point('1234567890.1234567890', $_);
}
sub move_point {
my ($v, $n) = #_;
$v = Math::BigFloat->new($v);
# Build 10**$n
my $mul = Math::BigFloat->new(10)->bpow($n);
# Count new decimal places
my $dp = $v =~ /\.([^.]*)\z/ ? length $1 : 0;
$dp += $n;
$v->bdiv($mul);
$v->bfround(-$dp) if $dp >= 0;
$v->bstr;
}
output
123456789012345678900000000000
12345678901234567890000000000
1234567890123456789000000000
123456789012345678900000000
12345678901234567890000000
1234567890123456789000000
123456789012345678900000
12345678901234567890000
1234567890123456789000
123456789012345678900
12345678901234567890
1234567890123456789
123456789012345678.9
12345678901234567.89
1234567890123456.789
123456789012345.6789
12345678901234.56789
1234567890123.456789
123456789012.3456789
12345678901.23456789
1234567890.123456789
123456789.0123456789
12345678.90123456789
1234567.890123456789
123456.7890123456789
12345.67890123456789
1234.567890123456789
123.4567890123456789
12.34567890123456789
1.234567890123456789
0.1234567890123456789
0.01234567890123456789
0.001234567890123456789
0.0001234567890123456789
0.00001234567890123456789
0.000001234567890123456789
0.0000001234567890123456789
0.00000001234567890123456789
0.000000001234567890123456789
0.0000000001234567890123456789
0.00000000001234567890123456789

TCL, replacing strings in a textfile

lets say I open a file, then parsed it into lines. Then I use a loop:
foreach line $lines {}
e.g., if the file contained the following string:
XYDATA, NAME1
I want to put ACC_ after the XYDATA to get ACC_NAME1
and if the file contains more than one strings with XYDATA, put VEL_, DSP_ and Prs_ and so on
Using the textutil::split package from tcllib, and the ability of foreach to iterate over multiple lists simultaneously
package require textutil::split
set line {XYDATA, foo, bar, baz, qux}
set prefixes {ACC_ VEL_ DSP_ Prs_}
set fields [textutil::split::splitx $line {, }]
set new [list]
if {[lindex $fields 0] eq "XYDATA"} {
lappend new [lindex $fields 0]
foreach prefix $prefixes field [lrange $fields 1 end] {
lappend new $prefix$field
}
}
puts [join $new ", "]
XYDATA, ACC_foo, VEL_bar, DSP_baz, Prs_qux
alternately, use a single regsub call that generates some code
set code [regsub -all {(, )([^,]+)} $line {\1[lindex $prefixes [incr counter]]\2}]
set counter -1
puts [subst $code]

Please help me with the simple tcl code

It is a simple code which gives the output when a number is raised to another number.But it is always returning the square of the number and not looping.
Please help guys
#!/usr/bin/tclsh
proc raise {{base} {pow} args} {
for {set base1 $base} {$pow >= 0} {incr $pow -1} {
set ans [expr $base * $base1 ]
set base $ans
return $ans
}
}
You are allowing the loop to go through at most 1 iteration. This is because return quits the current proc (the loop as well automatically).
Fixing that part would give:
#!/usr/bin/tclsh
proc raise {{base} {pow} args} {
for {set base1 $base} {$pow >= 0} {incr $pow -1} {
set ans [expr $base * $base1 ]
set base $ans
}
return $ans
}
But.. that doesn't quite give you the answer, does it. The logic of your code is not quite correct. I think it should be:
proc raise {base pow} {
for {set base1 $base} {$pow > 1} {incr pow -1} {
set ans [expr {$base * $base1}]
set base $ans
}
return $ans
}
incr takes a variable name, not a variable, and you want to iterate until the power is above 1. If it is 1, then you get the base, hence you don't loop. The last change was to brace the expressions. To make the above work for powers of 0 as well, you can use Peter's proc.
But all that said, why don't you use the inbuilt operator for this?
set ans [expr {$base**$pow}]
or even:
set ans [expr {pow($base,$pow)}]
A slightly less messy solution which gives a correct answer for $pow = 0 too:
proc raise {base pow} {
for {set answer 1} {$pow > 0} {incr pow -1} {
set answer [expr {$answer * $base}]
}
return $answer
}
It's often useful, when experimenting with code, to be stingy with variables and command invocations: when you're sure that you can't eliminate any more of them, you probably have fairly efficient, readable, and robust code.
Documentation: expr, for, incr, proc, return, set
just a first-glance-answer: don't you return too early?
#!/usr/bin/tclsh
proc raise {{base} {pow} args} {
for {set base1 $base} {$pow >= 0} {incr $pow -1} {
set ans [expr $base * $base1 ]
set base $ans
}
return $ans
}

What's the mistake in my recursive subroutine?

This subroutine generates string combinations of the letters using the letters from A to the Mth letter of the Alphabet with length N.
sub genString
{
my($m,$n,$str,$letter,$temp,$i) = #_;
if($n == 0){
$letter = chr(ord("A")+($i+=1));
if($temp == 1){ print "$str\n"; }
else{
for($j = 0 ; $j < temp-1 ; $j++){
if(ord(substr($str,$j,1)) < ord(substr($str,$j+1,1))){$do_print = 1;}
else{
$do_print = 0;
break;
}
}
if($do_print == 1){ print "$str\n"; }
}
}
else{
for($j = ord($letter) ; $j < ord($letter)+$m ; $j++){
genString($m,$n-1,$str.chr($j),$letter,$temp,$i);
}
}
}
&genString($m,$n,$str,"A",$n,0);
Example:
Input: M=4; N=3;
Output: ABC ABD ACD BCD
I tried similar to this in Ruby and it works, but in Perl, it's an infinite loop, and I don't know why. I'm new here in Perl. What should I do? (Sorry if my code is kinda lengthy)
Please always use use strict; and use warnings; in your code, especially when posting code and asking for help. Also always declare local variables with my.
In this case even without having tried it I'm pretty sure something like $j referring to a global variable is causing you a lot of headache -- something use strict would have caught.
By default, variables are globals in perl (though undeclared and unqualified use of them will be prevented by use strict). For your recursion to work, you'll need to make some of them lexical, for instance, changing:
for($j = 0 ; $j < temp-1 ; $j++){
to
for (my $j = 0; $j < $temp-1; $j++) {
or better yet, just
for my $j (0..$temp-2) {
Your code is very hard to read. I can't understand the algorithm, and I don't see the purpose of so many parameters to the subroutine, especially $temp which doesn't appear to change, and you don't say what its initial value is set to in the outermost call.
This code appears to do what you want, with a similar algorithm
use strict;
use warnings;
genString(4, 3);
sub genString {
my ($m, $n, $str, $i) = #_;
if ($n == 0) {
print $str, "\n";
}
else {
for my $off ($i // 0 .. $m - $n) {
$str //= '';
genString($m, $n-1, $str.chr(ord('A') + $off), $off+1);
}
}
}
output
ABC
ABD
ACD
BCD

Resources