Array manipulation in TCL - tcl

I have the following data each in separate arrays such as atten(), power(), bandwidth(), Time()
Atten Power Bandwidth Time
30 1.52E+01 52638515 0
31 1.51E+01 49807360 10
32 1.46E+01 52848230 20
33 1.51E+01 39845888 30
I need to change the arrangement to the following format
Atten Power Bandwidth Time
30 1.52E+01 52638515 0
30 1.52E+01 49807360 10
31 1.51E+01 52848230 20
31 1.51E+01 39845888 30
. . . .
Now i need to make atten() and power() appear twice without changing anything for the arrays bandwidth() and time in an excel in csv format???.. The following is how i write my data to excel in CSV format.
set application [::tcom::ref createobject "Excel.Application"]
set XlFileFormat(xlCSV) [expr 6]
set workbooks [$application Workbooks]
set workbook [$workbooks Add]
$application DisplayAlerts False
set worksheets [$workbook Worksheets]
set worksheet [$worksheets Item [expr 1]]
set cells [$worksheet Cells]
set rows [array size atten]
for {set row 1} {$row <= $rows} {incr row} {
$cells Item $row "A" $atten($row)
}

The following code snippet is example of how to use a chronological list of array keys, in order to print out array contents in order. This is then used to make an array with two of every element.
#!/usr/bin/tclsh
proc add_element {array_name key value} {
upvar $array_name aa
if { ![info exists aa($key)] } {
set aa($key) $value
lappend aa() $key
}
}
add_element names 1 Jane
add_element names 2 Tom
add_element names 3 Elisabeth
add_element names 4 Ted
add_element names 5 Sally
foreach e $names() {
add_element morenames $e $names($e)
add_element morenames $[expr $e + 1 ] $names($e)
}
foreach e $morenames() {
puts $morenames($e)
}
In order to solve your problem you would generate new arrays for atten and power with double element as per the above example. You would then generate your other arrays using a function similar to the example above, unless they already are returned ordered when iterating through them.
You would then iterate through on of the other arrays, say newTime, using something similar to the snippet below:
set rows [array size newTime]
for {set row 1} {$row <= $rows} {incr row} {
$cells Item $row "A" $newatten($row)
$cells Item $row "B" $newpower($row)
$cells Item $row "C" $newbandwidth($row)
$cells Item $row "C" $newTime($row)
}
The following code snippet is an example of how to remap numeric keys of a TCL Array (Associative array or HashMap).
#!/usr/bin/tclsh
set names(1) Jane
set names(2) Tom
set names(3) Elisabeth
set names(4) Robert
set names(5) Julia
set names(6) Victoria
foreach n [array names names] {
puts $n
puts $names($n)
}
puts "-------------"
foreach n [array names names] {
set newnames([expr $n -1]) $names($n)
}
foreach n [array names newnames] {
puts $n
puts $newnames($n)
}
This isn't sufficient to do what you want.
You would have to do this and then remove unset the first element and add set a last element.
You haven't specified in your question what your last element would be.
If you are using non numeric keys, e.g string keys then you would have to give those keys some concept of order, whether by mapping them to numeric keys or using some other method.
Note that TCL arrays don't return things in order in a foreach loop unless you tell them to.
TCL lists are better suited to that, as demostrated by.
#!/usr/bin/tclsh
set i 0
foreach j "a b c" {
puts "$j is item number $i in list x"
incr i
}
You also haven't specified what data structures you want to convert to or whether you just want to write the input to screen (using puts) or to file.
E.g Whether you would to generate any of the folllowing data structures for your table ?
A TCL array of TCL arrays (Hashmap of Hashmap)
A List of Lists
A TCL array of Lists
A List of TCL Arrays (List of Hashmap)
For further information see:
Arrays Page TCL Wiki
List Page TCL Wiki
Associative Arrays Page TCL Tutorial
List Page TCL Tutorial

Related

String pattern matching with tcl

I am new to tcl and am trying to only capture a user-specified hierarchy depth of the following:
top.run.end
top.run.something.end
top.simple.end1
top.simple.end2
top.simple.something.end1
top.simple.something.end2
top.simple.something.else.end
top.simple.something.else.other.name.end
I would like to only capture the final element in a hierarchy that does not continue with more elements deliminated by a ".". I.e. I would like to append all instances to a list (final element name could be anything).
If the user wants to select the 2nd hierarchy level, the comparison should only allow these elements from above:
top.run.end
top.simple.end1
top.simple.end2
If the user specifies the 3rd hierarchy level, then I would like to grab these elements:
top.simple.something.end1
top.simple.something.end2
4th hierarchy level:
top.simple.something.else.end
So on and so forth... I have all the code written except the string comparison, but everything I've tried doesn't seem to do what I want.
set num_hierarchy 3; # how many levels deap to search for "end"
set num_facs [ gtkwave::getNumFacs ]; # returns number of elements in file
for {set group_to_add 1} {$group_to_add <= $num_hierarchy} {incr group_to_add} {
set wave [list]
for {set i 0} {$i < $num_facs } {incr i} {
set fac_name [ gtkwave::getFacName $i ]; #returns string in form noted above
set indx [string <how to compare strings??> $fac_name]
if {$indx == <match>} {
lappend wave "$fac_name"
}
}
}
I can't say I understand why you are doing the loops like in your question, so I'll show a slightly different code snippet. I believe you should easily be able to implement the solution in your own if I can show you how mine works. The matching is done by counting the number of dots:
set elements {
top.run.end
top.run.something.end
top.simple.end1
top.simple.end2
top.simple.something.end1
top.simple.something.end2
top.simple.something.else.end
top.simple.something.else.other.name.end
}
set depth_required 3
set wave [list]
foreach element $elements {
# initial length of element
set i_len [string length $element]
# final length of element after removing dots
set f_len [string length [string map {. ""} $element]]
# thus number of dots
set n_dots [expr {$i_len-$f_len}]
# if the number equals the required hierarchy, then we got one
if {$n_dots == $depth_required} {
lappend wave $element
}
}
wave then contains:
top.run.something.end top.simple.something.end1 top.simple.something.end2
You could use regsub (which can directly return the number of substitutions performed) or split the element on dots then count of the number of resulting sub-elements as well, but I found this wiki where it shows using string map is the fastest method overall.

function to calculate the sum of a given array

I am new to Tcl so i am learning the basics. I wrote a function to calculate the sum of an array and print its elements. Here is the code
proc print_sum { tab } {
set s 0
foreach key [array names tab] {
puts "${key}=$tab($key)"
incr $s $tab($key)
}
puts "the sum = $s"
}
Here is how I called it:
print_sum tab
and I created the tab like this:
set tab("1") 41
set tab("m2") 5
set tab("3") 3
set tab("tp") 9
set tab("2") 7
set tab("100") 16
But the output is wrong! It outputs 0 instead of the actual sum and it does not output any element. But when I used the code directly without writing it in a function, it works.
The issue is that you're passing the string "tab" to the proc, and then you store that in the variable name "tab". This is just a plain variable, not an array, so when you do array names tab, you get an empty list back. The foreach loop loops zero times, and the sum is still zero.
You need to use the upvar command to link to the "tab" array in the caller's stack frame:
proc print_sum { arrayName } {
upvar 1 $arrayName a ;# "alias" the array in the caller's scope
set s 0
foreach key [array names a] {
puts "${key}=$a($key)"
incr s $a($key) ;# increment the *variable* not the *variablevalue*
}
puts "the sum = $s"
}
print_sum tab
outputs
"tp"=9
"100"=16
"1"=41
"2"=7
"3"=3
"m2"=5
the sum = 81

Extract information from a list using Tcl

I have multiple log files which contain values like this with headers :
I want to make a header file which contains each row from column 1 as individual column headers and min - max from each of the row and present it in column format.
Info in log files:
Trace Header Min Max Mean
aaa 1 6 xx
bbb 2 7 xxx
What I want :
aaa bbb
1-6 2-7
Thanks for help
Try this (the long listing is supposed to be in the data variable, read from a file or whatever):
foreach line [split $data \n] {
if {[scan $line {%s %d %d} header min max] eq 3} {
set result($header) $min-$max
}
}
% parray result
result(aaa) = 1-6
result(bbb) = 2-7
The scan command looks for three fields on each line, one text field and two decimal integer fields. A matching line reports three fields found, empty lines or lines with only text report less. If it finds a match, it is added to the result.
ETA:
To deal with the real-world log file you mentioned in a comment:
foreach line [split $data \n] {
if {[scan $line {%59[ #()-./0-9:=>A-Za-z]%s %d %d} header stuff min max] eq 4} {
set result([string trim $header]) $min-$max
}
}
(Note that duplicate headers are compacted into one in the array.)
If you have whitespace in a field, you can't consume the data with %s. Instead you can find out what kind of data the header might contain by using
% set chars [string map {\n {}} [join [lsort -unique [split $data {}]] {}]]
#()-./0123456789:=>ABCDEFGHILMNOPRSTUVWXY[]abcdefghijklmnopqrstuvwxyz
which is easy to simplify to the field specification
[ #()-./0-9:=>A-Za-z]
If you need to able to match brackets, put them in like this:
[][ #()-./0-9:=>A-Za-z]
To split at lines containing uppercase text and blanks, then only equal-signs and possibly more blanks up to line end,
package require textutil::split
::textutil::splitx $data {(?n)^[[:upper:] ]+=+\s*$}
Documentation:
eq (operator),
foreach,
if,
join,
lsort,
package,
parray,
regexp,
Syntax of Tcl regular expressions,
scan,
set,
split,
string,
textutil::split (package)
Code snippet:
set foo {
Info in log files:
Trace Header Min Max Mean
aaa 1 6 xx
bbb 2 7 xxx
}
set pattern {^(.*)\s+(\d+)\s+(\d+)\s+.*$}
set result [regexp -line -inline -all -- $pattern $foo]
array set bar {}
puts "Here's one view..."
foreach {all item min max} $result {
puts "$item $min-$max"
set bar([string trim $item]) $min-$max
}
puts ""
puts "Here's another one..."
puts [join [lsort [array names bar]] "\t"]
foreach item [lsort [array names bar]] {
puts -nonewline "$bar($item)\t"
}
Execution output:
Here's one view...
aaa 1-6
bbb 2-7
Here's another one...
aaa bbb
2-7 1-6

Foreach for TCL List

I would like to ask a question regarding TCL.
Let say I have an instance 111 and under this 3 instances (11100, 11102, 11103) are attached. You may say 11100, 11102, 11103 are id's and then there are names attached to these ids such as A, B, C.
At the moment I got all these ids 11100, 11102, 11103 how can use these ids in loop to iterate in loop for three time so I can find each ids name.
This is how you can iterate loop over the ids what you already have,
foreach i {11101 11102 11103} {
puts $i
;# do what ever with i
}
Another option what you said is you have three variable A B C,
lappend list $A $B $C
foreach i $list {
puts $i
;# do what ever with i
}

Combinations of all charcaters and all lengths with using less number of loops?

Brain Teaser: I self originated this question, but stuck completely.
I want to create all possible combination of all characters, but of all possible lengths. Suppose, [a-z] combination of 1 length, then [a-z] combination of 2 length, and so on till the maximum length achieved.
this could be very easily done by iterative looping.
Example for 3 length:
proc triples list {
foreach i $list {
foreach j $list {
foreach k $list {
puts [list $i $j $k]
}
}
}
}
But, it should solve using less loops (looping needs to be dynamic)
set chars "abcdefghijklmnopqrstuvwxyz"
set chars [split $chars ""]
set complete_length [llength $chars]
set start 0
set maximum_length 15
while {1} {
if {$start > $maximum_length} {
break
}
for {set i [expr $maximum_length-$start]} {$i >= 0} {incr i -1} {
# dump combinations
}
incr start
}
In this chunk, what algorithm or method i should apply? Any kind of suggestions/help/code will be appreciated.
Sry, this is not an answer, but hopefully some interesting discussion anyway:
The word "combinations" is often used way too generally, so it can be interpreted in many different ways. Let's say that you have a source list of 26 different elements, the english letters, and you want to pick 3 of them and combine in a 3 element destination list:
Can you always pick any letter from the source list, or do the elements disappear from it as you pick them? Either define "pick" (are the elements copied or moved during a pick), or define the set of source values (is there 1 of each of A-Z or an infinite amount of A-Z).
Does the order in the destination list matter? Is AHM considered to be the same combination as HAM? Define "combine".
If you have a list where not all elements are different, e.g. {2 10 10 64 100}, you have even more possibilities. Define your set of values.
Your first example prints permutations, not combinations. If that's what you want, the easiset way is a recursive procedure. Combinations are more complicated to generate.
EDIT:
I wrote this procedure for a Project Euler program. It picks all the elements, but maybe you can modify it to pick n. It takes a command prefix as argument, so you don't have to store all permutations.
package require Tcl 8.5.0
proc forEachPerm {list cmdPrefix} {
_forEachPerm {} $list $cmdPrefix
}
proc _forEachPerm {head list cmdPrefix} {
if {![llength $list]} {
{*}$cmdPrefix $head
} else {
for {set i 0} {$i < [llength $list]} {incr i} {
_forEachPerm [concat $head [lrange $list $i $i]] [lreplace $list $i $i] $cmdPrefix
}
}
}
# example use:
forEachPerm {a b c} {apply {{list} {puts [join $list]}}}