I have this file
2 1
12 2
34 1
56 1
45 3
33 2
77 1
83 2
62 3
75 3
I want to take the first value from second column with the smallest value from column 1
like this
2 1
12 2
45 3
This can be done with a linear scan and a little bit of use of an associative array, something like this:
set f [open $filename]
foreach line [split [read $f] "\n"] {
# ASSUME: valid Tcl list of numbers
lassign $line col1 col2
if {![info exists minima($col2)] || $minima($col2) > $col1} {
set minima($col2) $col1
}
}
close $f
foreach col2 [array names minima] {
puts "$minima($col2) $col1"
}
Imposing whatever sorts of parsing, sorting and formatting you require are left to you.
Related
I have a file which has the following kind of data
A 1 2 3
B 2 2 2
c 2 4 5
d 4 5 6
From the above file I want to execute a loop like ,
three iteration where first iteration will have A,B elements 2nd iteration with c elements and 3rd with d. so that my html table will look like
Week1 | week2 | week3
----------------------------
A 1 2 3 | c 2 4 5 | d 4 5 6
B 2 2 2
I found this in SO catch multiple empty lines in file in tcl but I'm not getting what I exactly want.
I would suggest using arrays:
# Counter
set week 1
# Create file channel
set file [open filename.txt r]
# Read file contents line by line and store the line in the varialbe called $line
while {[gets $file line] != -1} {
if {$line != ""} {
# if line not empty, add line to current array with counter $week
lappend Week($week) $line
} else {
# else, increment week number
incr week
}
}
# close file channel
close $file
# print Week array
parray Week
# Week(1) = {A 1 2 3} {B 2 2 2}
# Week(2) = {c 2 4 5}
# Week(3) = {d 4 5 6}
ideone demo
I would like to know the most robust method for extracting Y Value for a given X Value from column of X-Y data.
I am currently performing this operation with the following code, but is very unreliable/flakey as it keeps falling over with error of can't read or no variable var_01
Please advice.
Iterate based on Column Z
for {set i 0} {$i < [llength $Col_z]} {incr i} {
set Xdata [lindex $Col_x $i]
set Ydata [lindex $Col_y $i]
lappend var $Ydata
if { $Xdata >= 0.9 && $Xdata <= 1.1 } {
set a [lindex $var $i]
lappend var_01 $a
} else {lappend var_01 0
#set var_01 0}
}
It's very hard to work out what you want to do, but maybe it helps to simplify the code a bit:
foreach z $Col_z x $Col_x y $Col_y {
if {$z eq {}} {
break
}
if {$x >= 0.9 && $x <= 1.1} {
lappend var_01 $y
} else {
lappend var_01 0
}
}
Edit according to comment: is this better?
set var_01 {}
foreach z $Col_z x $Col_x y $Col_y {
if {$z eq {}} {
break
}
if {$x >= 0.9 && $x <= 1.1} {
lappend var_01 $y
}
}
Note that var_01 might be empty if no value of x is within the range.
Documentation:
&& (operator),
<= (operator),
>= (operator),
break,
eq (operator),
foreach,
if,
lappend,
set
A very convenient way to represent tables in tcl is by simple array. Here is an example:
array set xy {}
foreach i {1 2 3} {
foreach j {10 20 30} {
set xy($i,$j) [expr $i + $j]
}
}
Now xy is an array whose keys look like table indexes. Here:
% array names xy
3,10 2,20 1,30 3,20 2,30 3,30 1,10 2,10 1,20
Or more clear:
% foreach k [array names xy] {puts $k}
3,10
2,20
1,30
3,20
2,30
3,30
1,10
2,10
1,20
Here is how to access them:
% puts $xy(3,10)
13
The 3,10 inside the parenthesis is a string! The array returns the value associated with that string, which was associated in the above loop. (Therefore there must not be space after the comma).
It's easy to access the values if the indexes are given in variables:
% set x 3
3
% set y 10
10
% puts $x,$y
3,10
The last command is equivalent to explicit quotation marks:
% puts "$x,$y"
3,10
And here is how we access the array element at that key:
% puts $xy($x,$y)
13
And if the key doesn't exist:
% puts $xy(4,10)
can't read "xy(4,10)": no such element in array
Let's conclude with printing the keys and values of the array:
% foreach k [array names xy] {puts "$k: $xy($k)"}
3,10: 13
2,20: 22
1,30: 31
3,20: 23
2,30: 32
3,30: 33
1,10: 11
2,10: 12
1,20: 21
ADDED
Now suppose you have the y and z values, how do you find the x?
set y 20
set z 23
Using the special, powerful tcl property of everything is a string:
Here we find all keys and values matching the key pattern *,20:
set results [array get xy *,$y]
Let's see:
puts $results
% 2,20 22 3,20 23 1,20 21
We got a list of 3 pairs, each contains the key and value.
Now let's extract the key/value that corresponds to outr $z. We will use the powerful regexp tcl command, seeing $results now as a string instead of a list:
regexp "(\\d+),($y) ($z)" $results whole x1 y1 z1
And now x1, y1, z1 hold all the information we want:
puts "$x1 $y1 $z1"
% 3 20 23
I have a group of lists some with strings, some with numbers and some with both. All these lists have variable lengths. I would like to know what would be the best way to print it to a file so that they all have equal spacing between them.
For example, I use,
set numbers {0 1 2 3 4}
set type {dog reallybigbaddog thisisaevenlargersentence cat bird}
set paths {aaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ccc ddddddddddddddddd efgh}
puts $fid "NUMBERS\t\tTYPE\tPATHS"
foreach numbersval $numbers typeval $type pathsval $paths {
puts $fid "$numbersval\t\t$typeval\t$pathsval"
}
The result was,
NUMBERS TYPE PATHS
0 dog AAA
1 reallybigbaddog bbbbbbbbbbbbbbbbbbbbbbbb
2 thisisaevenlargersentence ccc
3 cat ddddddddddddddddd
4 bird efgh
I Tried using "format" based on one of the suggestions on this site but that resulted in a similar output, I guess we need a way to determining what the longest string is and cant arbitrarily use "\t"? Would appreciate any better suggestions.
For reference, this is how you could do it with struct::matrix and report:
package require struct::matrix
package require report
set nrows 5
set ncols 3
set npads [expr {$ncols + 1}]
struct::matrix m
m add rows $nrows
m add column {0 1 2 3 4}
m add column {dog reallybigbaddog thisisaevenlargersentence cat bird}
m add column {aaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ccc ddddddddddddddddd efgh}
m insert row 0 {NUMBERS TYPE PATHS}
report::report r $ncols
r data set [lrepeat $npads \t]
m format 2string r
(This uses only a fraction of the formatting power of report.) This method can handle values with spaces in them.
Result (there is a tab character to the left of the first column on each row, but it's lost in the formatting here.):
NUMBERS TYPE PATHS
0 dog aaa
1 reallybigbaddog bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 thisisaevenlargersentence ccc
3 cat ddddddddddddddddd
4 bird efgh
Documentation: expr, lrepeat, package, report package, set, struct::matrix package
In this case, I'd call out to column -t to do the work for me:
set all "NUMBERS TYPE PATHS\n"
foreach n $numbers t $type p $paths {
append all "$n $t $p\n"
}
set formatted [exec column -t << $all]
puts $formatted
NUMBERS TYPE PATHS
0 dog aaa
1 reallybigbaddog bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 thisisaevenlargersentence ccc
3 cat ddddddddddddddddd
4 bird efgh
A pure Tcl way to do this:
array set maxl {numbers 0 type 0 paths 0}
foreach l {numbers type paths} {
foreach e [concat $l [set $l]] {
if {[set len [string length $e]] > $maxl($l)} {
set maxl($l) $len
}
}
}
puts [format "%-*s %-*s %-*s" $maxl(numbers) NUMBERS $maxl(type) TYPE $maxl(paths) "PATH LISTS"]
foreach n $numbers t $type p $paths {
puts [format "%-*s %-*s %-*s" $maxl(numbers) $n $maxl(type) $t $maxl(paths) $p]
}
Suppose that a procedure in Tcl is as follows:
proc Section {ID x y} {
.
.
"Some calculations do here"
.
.
}
Section 1 20 30
Section 2 25 35
Section 3 30 40
Section 4 35 45
Now, I define this:
set IDsection {1 3}
Then, I would like to read all values (arbitrary number, 2 or more) into a set (IDsection) which would show the ID in above procedure and generate the corresponding y:
set Load {30 40}
How I do produce the values in {} in front of "Load"?
You could do it like this:
proc Section {ID x y} {
set ::section_data($ID) [list $x $y]
}
proc getLoads {ids} {
global section_data
foreach id $ids {
lappend loads [lindex $section_data($id) end]
}
return $loads
}
Section 1 20 30
Section 2 25 35
Section 3 30 40
Section 4 35 45
set IDsection {1 4 1 3}
set Load [getLoads $IDsection] ;# => 30 45 30 40
I'm a regular matlab user who needs to do some processing in TCL. I have no experience with tcl so up to now what I have done is by searching on google.
Please pardon novice ways...
I have data in column A and B which is imported via file Data_1, I would like to get the corresponding values from column B for two numbers in column A e.g.
when A is 0.1 then B is 9 and when A is 0.3 then B is 21, store 9 & 21 in variable for later use
I'd like to open another file Data_2, which has two columns C and D.
I'd like to take all the numbers of column D that fall between Column 9 and 21 (positive numbers) in Column C and average it and put it in a variable for later use.
I've started with first trying to find corresponding values for 0.1 and 0.3, and that is where I am stuck.
I can (I think) find 0.1 and 0.3 but don't know how to get the corresponding values from column B
And then proceed ahead with the second part.
Please help.
Data_1 Data__2
Column A Column B Column C Column D
0 0 180 14.5
0.01 1.5 162 13.05
0.02 3 144 11.6
0.03 4.5 126 10.15
0.04 6 108 8.7
0.05 7.5 90 7.25
0.1 9 72 5.8
0.125 10.5 54 4.35
0.15 12 20 2.9
0.175 13.5 10 1.45
0.2 15 0 0
0.225 16.5 -10 -1.45
0.25 18 -20 -2.9
0.275 19.5 -54 -4.35
0.3 21 -72 -5.8
0.325 22.5 -90 -7.25
0.35 24 -108 -8.7
0.0.375 25.5 -126 -10.15
0.4 27 -144 -11.6
0.425 28.5 -162 -13.05
0.45 30 -180 -14.5
# Open files for reading
set input1 [open "Data_1.dat" r]
set input2 [open "Data_2.dat" r]
#read file
set file_data [read $input1]
#close file
close $input1
#split into lines
set data [split $file_data "\n"]
foreach line $data {
set val1 [lsearch -inline $line 0.1]
set val2 [lsearch -inline $line 0.3]
puts $val1
puts $val2
}
I'd write this:
set min_key 0.1
set max_key 0.3
set fid [open Data_1.dat r]
while {[gets $fid line] != -1} {
lassign $line a b
if {$a == $min_key} {
set min $b
}
if {$a == $max_key} {
set max $b
}
}
close $fid
set fid [open Data_2.dat r]
while {[gets $fid line] != -1} {
lassign $line c d
if {$min <= $d && $d <= $max} {
lappend values $c
}
}
close $fid
puts [join $values \n]
This outputs
180
162
144
126
Notes:
use a while loop to iterate over the lines of a file.
lassign assigns values of a list to variables
I didn't quite follow all of the if/else logic in your post, but there are several ways you can process the files. From your example, you can store the A and B columns as two separate lists.
set acol [list]
set bcol [list]
set data [split $file_data "\n"]
foreach line $data {
lappend acol [lindex $line 0]
lappend bcol [lindex $line 1]
}
# Find the first column A value that is 0.1.
set index [lsearch -real $acol 0.1]
# Get the corresponding B value.
puts "b value: [lindex $bcol $index]"
You can probably also just simplify the foreach-loop to this, without splitting the file data into lines.
foreach {val1 val2} $file_data {
lappend acol $val1
lappend bcol $val2
}