I am trying to save a list of numbers in binary format (floating point single)
but Tcl cant save it correctly and I could not gain to correct number when i read the file from vb.net
set outfile6 [open "btest2.txt" w+]
fconfigure stdout -translation binary -encoding binary
set aa {}
set p 0
for {set i 1} {$i <= 1000 } {incr i} {
lappend aa [expr (1000.0/$i )]
puts -nonewline $outfile6 [binary format "f" [lindex $aa $p]]
incr p
}
close $outfile6
Tcl cant save it correctly
There are two glitches in your snippet:
the missing brackets for nested command evaluation around lindex (see my comment): [lindex $aa $p]
you fconfigured the stdout, rather than your file channel: fconfigure $outfile6 -translation binary
With this fixed, the following works for me:
set outfile6 [open "btest2.txt" w+]
fconfigure $outfile6 -translation binary
set aa {}
set p 0
for {set i 1} {$i <= 1000 } {incr i} {
lappend aa [expr (1000.0/$i )]
puts -nonewline $outfile6 [binary format "f" [lindex $aa $p]]
incr p
}
close $outfile6
Suggestions for improvement
Your snippet seems overly complicated to me, esp. the loop construct. Simplify to:
Better use [scan %f $value] to explicitly turn a value into the floating point representation, rather than [expr]?
[binary format] takes a counter or wildcard, like f*, to process multiple values: [binary format "f*" $aa]
You don't need the loop variables p, use [lindex $aa end]; or better a loop variable to hold the single added element (rather than to collect it from the list again).
-translation binary implies -encoding binary
Related
I have a data file (*.dat) containing x, y, z coordinates. As following:
{26.3612117767334 40.19668960571289 54.13957977294922}
{27.351043701171875 40.57518768310547 54.05387496948242}
{29.48208999633789 42.08218765258789 56.42238235473633}
For this file I need to do a math operation as follow:
Xi + (Xf-Xi/4) ; Yi + (Yf-Yi/4) ; Zi + (Zf-Zi/4)
where "i" is the initial position and "f" the final, meaning that Xi,Yi,Zi are the data on the first line and Xf,Yf,Zf the data on the second.
I need to do these calculation for all the lines in a loop and then stored in a separate file, but I do not have idea how to do it in TCL. Thanks in advance for your help.
Since the contents of your file can be treated as a bunch of tcl lists, one per line (so basically a list of lists), parsing it is dead simple.
Something like:
set f [open file.dat]
set coords [read -nonewline $f]
close $f
for {set i 0} {$i < [llength $coords] - 1} {incr i} {
lassign [lindex $coords $i] xi yi zi
lassign [lindex $coords $i+1] xf yf zf
set xn [expr {$xi + ($xf - $xi/4.0)}]
set yn [expr {$yi + ($yf - $yi/4.0)}]
set zn [expr {$zi + ($zf - $zi/4.0)}]
puts "{$xn $yn $zn}"
}
This skips treating the last line as an initial set of coordinates because there is no next set for it.
This is a good opportunity to write a mathfunc:
proc tcl::mathfunc::f {ai af} {
expr {$ai * 0.75 + $af}
}
proc transform {file} {
set fh [open $file]
# read the first line, aka the initial "previous line"
gets $fh line
scan $line {{%f %f %f}} xi yi zi
# process the rest of the file
while {[gets $fh line] != -1} {
scan $line {{%f %f %f}} xf yf zf
puts "{[expr {f($xi, $xf)}] [expr {f($yi, $yf)}] [expr {f($zi, $zf)}]}"
lassign [list $xf $yf $zf] xi yi zi
}
close $fh
}
transform file.dat
outputs
{47.121952533721924 70.72270488739014 94.65855979919434]}
{49.9953727722168 72.51357841491699 96.96278858184814]}
I present an alternate method that uses lrange to pick the overlapping ranges of sublists that participate (so we can then process them element-wise) and then lmap to apply the same transformation expression to each coordinate axis.
# Same read-in code as Shawn's answer; it's the easiest way
set f [open file.dat]
set coords [read -nonewline $f]
close $f
foreach Ci [lrange $coords 0 end-1] Cf [lrange $coords 1 end] {
# I often like to put expressions on their own line for clarity
puts [list [lmap _i $Ci _f $Cf {expr {
$_i + ($_f - $_i/4.0)
}}]]
}
(The wrapping list call in there puts braces around the result of lmap.)
I am new in tcl programming and I need to write a script for vmd that calculates two distances between two couples of atoms and print them in an output file. I do not understand why measure can not take atom_1, etc. This is my script and thank you for your help
proc distance {distance_1 atom_1 atom_2 distance_2 atom_3 atom_4 output} {
set outfile [open $output w]
puts $outfile "frame, $distance_1, $distance_2"
set nf [molinfo top get numframes]
for {set i 0} {$i < $nf} {incr i} {
set d1 [measure bond {$atom_1 $atom_2} frame $i]
set d2 [measure bond {$atom_3 $atom_4} frame $i]
puts $outfile "$i , $d1 , $d2"
}
close $outfile
}
The problem here is:
measure bond {$atom_1 $atom_2} frame $i
The issue is that {…} in Tcl actually means “quote this exactly, with no substitutions at all”. Instead of sending a list of two numbers in, it passes a list of two non-numbers (the literal strings $atom_1 and $atom_2).
The fix is to replace {$atom_1 $atom_2} with [list $atom_1 $atom_2].
Yes, proc and for and if make use of this behaviour. It's just that they pass things back to the Tcl interpreter engine as part of their execution.
Hi i'm new to tcl i'm trying to insert element to list in proc from user input and return the list and invoke it in another list
i have tried this and i'm get
puts "Enter list Size"
set size [gets stdin]
set aList [fillTheList $size]
proc fillTheList {arg1 } {
set lList {}
for {set i 0} {$i <= $arg1} {incr i} {
set value [gets stdin]
linsert $lList $i int(value)]
puts "[lindex $lList $i]"
}
return $lList
}
and i'm getting this error in cmd
invalid command name "fillTheList"
while executing
"fillTheList $size"
invoked from within
"set aList [fillTheList $size]"
(file "ascending.tcl" line 5)
Try
proc fillTheList {arg1 } {
set lList {}
for {set i 0} {$i < $arg1} {incr i} {
puts -nonewline "Enter value "
set value [gets stdin]
lappend lList $value
puts [lindex $lList $i]
}
return $lList
}
puts -nonewline "Enter list Size "
set size [gets stdin]
set aList [fillTheList $size]
A couple of notes:
If you set the condition in the for invocation to $i <= $arg1 it will ask for one more list item than you wanted, since i starts from 0.
Instead of lappend, lset lList $i $value could be used. It used to only be able to change elements already in the list, but nowadays it can change the element after the last one in the list, extending the list by one.
lList is a really bad variable name, because it is easy to mix up with names like IList.
Tcl is barely typed at all. You type strings from the keyboard, those strings are entered in the list. If those strings are valid integers they can be used like integers. You don't need, and you can't, convert them.
Documentation:
< (operator),
for,
gets,
incr,
lappend,
lindex,
lset,
proc,
puts,
return,
set
I know I have been asking a lot of questions but I'm still learning tcl and I haven't found anything that similar to this issue anywhere so far. Is it at all possible to replace a set f commands in tcl with one variable function0 for example?
I want to be able to replace the following code;
set f [listFromFile $path1]
set f [lsort -unique $f]
set f [lsearch -all -inline $f "test_*"]
set f [regsub -all {,} $f "" ]
set len [llength $f]
set cnt 0
with a variable function0 because this same code appears numerous times within the script. I should mention it appears both in a proc and not in a proc
The above code relates to similar script as
while {$cnt < $len} {
puts [lindex $f $cnt]
incr cnt
after 25; #not needed, but for viewing purposes
}
Variables are for storing values. To hide away (encapsulate) some lines of code you need a command procedure, which you define using the proc command.
You wanted to hide away the following lines
set f [listFromFile $path1]
set f [lsort -unique $f]
set f [lsearch -all -inline $f "test_*"]
set f [regsub -all {,} $f "" ]
set len [llength $f]
set cnt 0
to be able to just invoke for instance function0 $path1 and have all those calculations made in one fell swoop. Further, you wanted to use the result of calling the procedure in code like this:
while {$cnt < $len} {
puts [lindex $f $cnt]
# ...
Which means you want function0 to produce three different values, stored in cnt, len, and f. There are several ways to have a command procedure return multiple values, but the cleanest solution here is to make it return a single value; the list that you want to print. The value in len can be calculated from that list with a single command, and the initialization of cnt is better performed outside the command procedure. What you get is this:
proc function0 path {
set f [listFromFile $path]
set f [lsort -unique $f]
set f [lsearch -all -inline $f test_*]
set f [regsub -all , $f {}]
return $f
}
which you can use like this:
set f [function0 $path1]
set len [llength $f]
set cnt 0
while {$cnt < $len} {
puts [lindex $f $cnt]
incr cnt
after 25; #not needed, but for viewing purposes
}
or like this:
set f [function0 $path1]
set len [llength $f]
for {set cnt 0} {$cnt < $len} {incr cnt} {
puts [lindex $f $cnt]
after 25; #not needed, but for viewing purposes
}
or like this:
set f [function0 $path1]
foreach item $f {
puts $item
after 25; #not needed, but for viewing purposes
}
This is why I didn't bother to create a procedure returning three values: you only really needed one.
glenn jackman makes a very good point (or two points, actually) in another answer about the use of regsub. For completeness, I will repeat it here.
Tcl is a bit confusing because it usually allows string operations (like string substitution) on data structures that aren't formally strings. This makes the language very powerful and expressive, but also means that newbies do not always get the kick in the shins that a regular type system would give them.
In this case you created a list structure inside listFromFile by reading a string from a file and then using split on it. From that point on it's a list and you should only perform list operations on it. If you wanted to take out all commas in your data you should either perform that operation on each item in the list, or else perform the operation inside listFromFile, before splitting the text.
String operations on lists will work, but sometimes the result will be garbled, so mixing them should be avoided. The other good point was that in this case string map is preferable to regsub, if nothing else it makes the code a bit clearer.
Documentation: for, foreach, lindex, llength, lsearch, lsort, proc, puts, regsub, set, split, string, while
(more of a comment than an answer, but I want the formatting)
One thing to be aware of: $f holds a list, then you use the string command regsub on it, then you treat the result of regsub as a list again.
Use list commands with list values. I'd replace the regsub command with
set f [lmap elem $f {string map {"," ""} $elem} ]
for Tcl version 8.5 or earlier, you could do this:
for {set i 0} {$i < [llength $f]} {incr i} {
lset f $i [string map {, ""} [lindex $f $i]]
}
How do I read more than a single line in a file using tcl? That is by default the gets command reads till a new line is found, how do I change this behaviour to read a file till a specific character is found?
If you don't mind reading over a bit, you can do it by looping with gets or read in a loop:
set data ""
while {[gets $chan line] >= 0} {
set idx [string first $whatToLookFor $line]
if {$idx == -1} {
append data $line\n
} else {
# Decrement idx; don't want first character of $whatToLookFor
append data [string range $line 0 [incr idx -1]]
break
}
}
# Data has everything up to but not including $whatToLookFor
If you're looking for multiline patterns, I suggest reading the whole file into memory and working on that. It's just so much easier than trying to write a correct matcher:
set data [read $chan]
set idx [string first $whatToLookFor $data]
if {$idx > -1} {
set data [string range $data 0 [incr idx -1]]
}
This latter form will also work just fine with binary data. Just remember to fconfigure $chan -translation binary first if you're doing that.
Use fconfigure.
set fp [open "somefile" r]
fconfigure $fp -eofchar "char"
set data [read $fp]
close $fp
In addition to Donal's good advice, you could get a list of records by reading the whole file and splitting on the record separator:
package require textutil::split
set records [textutil::splitx [read $chan] "record_separator"]
Documentation