Can we create a list of arrays and how? - tcl

I want to create a list and each element of it is an array, similarly to an array of structs in C language.
Can it be done in TCL and how if it can? thanks very much!
I did some try but it failed...
tcl>set si(eid) -1
tcl>set si(core) 0
tcl>set si(time) 0
tcl>lappend si_list "$si"
Error: can't read "si": variable is array

You can't create a list of arrays, but you can create a list of dicts which is functionally the same thing (a mapping from keys to values):
set mylist [list [dict create a 1 b 2] [dict create a 4 b 5]]
puts [dict get [lindex $mylist 1] a]
To do it as arrays you need to use [array get] and [array set] to change the array into a string:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list [array get si]
And to get it back out
array set newsi [lindex $si_list]
puts $newsi(eid)
dicts let you work on the {name value} lists directly.

One way to do this on versions of Tcl that don't include dict is to use upvar.
To do this, add the names of the array variables to your list:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list "si"
Then to get your array back, do this:
upvar #0 [lindex $si_list 0] newsi
puts $newsi(eid)

You could also use the ::struct::record package from tcllib for something like that.

Related

sorting a tcl dictionary inside txt file

I need some help with writing a tcl code, to sort the data from a dictionary. The dictionary saves lists in a .txt file. I need to access the file and sort it through the second column of the lists.
1,0.8,bananas,,,,,
2,1.0,apples,,,,,
3,5.1,grapes,,,,,
4,2.4,oranges,,,,,
5,1.7,pineapples,,,,,
...
how can i sort that dict data, to look like that:
1,0.8,bananas,,,,,
2,1.0,apples,,,,,
5,1.7,pineapples,,,,,
4,2.4,oranges,,,,,
3,5.1,grapes,,,,,
...
please, can you help me making this sorting code?
i tried many codes, but with no sucess.
Tcl's lsort -command option would be useful here.
To use it, first define a proc that takes two arguments (usually called a and b) which returns -1, 0, or 1. Each pair of items in the list will be used as a pair of arguments to this proc.
set lines {
1,0.8,bananas,,,,,
2,1.0,apples,,,,,
3,5.1,grapes,,,,,
4,2.4,oranges,,,,,
5,1.7,pineapples,,,,,
}
proc sort_by_col2 {a b} {
set list_a [split $a ","]
set list_b [split $b ","]
set a2 [lindex $list_a 1]
set b2 [lindex $list_b 1]
if {$a2 < $b2} {
return -1
} elseif {$a2 > $b2} {
return 1
} else {
return 0
}
}
lsort -command sort_by_col2 $lines
--> 1,0.8,bananas,,,,,
2,1.0,apples,,,,,
5,1.7,pineapples,,,,,
4,2.4,oranges,,,,,
3,5.1,grapes,,,,,

TCL : array is a collection of variables

I am trying to understand the difference between TCL arrays and TCL dictionary
There I get a statement :- "TCL array is a collection of variables while TCL dictionary is a collection of values"
Can somebody explain what it actually means. Programming example to understand the same will be great
Thanks
With an array, these are actually three separate variables.
set my_array(a) 1
set my_array(b) 2
set my_array(c) 3
Each variable exists separately:
info exists my_array(a) --> 1
info exists my_array(b) --> 2
info exists my_array(c) --> 3
puts "$my_array(a) $my_array(b) $my_array(c)" --> 1 2 3
The name my_array is also recognized to exist, but is actually a special name for the collection of the three individual variables. It's not really a regular variable that has its own value.
info exists my_array --> 1
puts $my_array --> can't read "my_array": variable is array
With a dictionary, there is one variable. The value of the variable represent the key/value pairs.
set my_dict "a 1 b 2 c 3"
info exists my_dict --> 1
info exists my_dict a --> wrong # args: should be "info exists varName"
puts $my_dict --> {a 1 b 2 c 3}
dict keys $my_dict --> {a b c}
dict get $my_dict a --> 1
dict values $my_dict --> {1 2 3}
In this example, the value of my_dict is {a 1 b 2 c 3}. You can pass $my_dict as an argument to a proc and the value is used by the proc. You can also return a dict from a proc. You cannot do that with an array in the same way.
One of the big differences is that you can trace add variable on an element of an array, whereas you can pass a dictionary into a procedure and return it from a procedure without needing to fiddle around with upvar or array get/array set.
proc didSetOnVarElement {name1 name2 op} {
puts "did $op on ${name1}($name2)"
}
trace add variable x(a) write didSetOnVarElement
set x(a) 1
set x(b) 2
incr x(a)
# Try comparing the output of that with what you get when you set a trace on the whole of x
Tracing array elements can be really useful, especially in Tk. So can passing things back and forth cheaply. You can't have them together though: values don't have identity in Tcl, whereas variables do (and it is exactly that identity — that name — that makes them modifiable and traceable entities while preventing them from being passed around trivially).
In addition to Chris's excellent answer:
A dictionary can be passed to a proc as a value
proc printItByValue {value} {
puts $value
}
set myDict [dict create a 1 b 2 c 3] ;# just another way to create a dict
printItByValue $myDict ;# => a 1 b 2 c 3
An array must be passed to a proc differently:
array set myArray {a 1 b 2 c 3} ;# just another way to populate an array
printItByValue $myArray ;# error as shown by Chris
printItByValue myArray ;# only prints the string "myArray"
We could extract the arrays contents and pass that to a proc as a list:
proc printArrayByValue {arrayContents} {
array set localArray $arrayContents
parray localArray
}
printArrayByValue [array get myArray]
localArray(a) = 1
localArray(b) = 2
localArray(c) = 3
parray is a handy utility proc to pretty-print an array.
Or, we can use the upvar command to link the passed variable name to the caller's stackframe.
proc printArrayByName {varName} {
upvar 1 $varName localArray
parray localArray
}
printArrayByName myArray ;# same output as above
Note that these procs print out the array using the local name. To get the array to print with the same name, we can do this tricky thing to link the local variable to the caller's variable with the same name:
proc printArrayByName {varName} {
upvar 1 $varName $varName
parray $varName
}
printArrayByName myArray ;# same output as above
myArray(a) = 1
myArray(b) = 2
myArray(c) = 3

print dictionary keys and values in one column in tcl

I am new learner of tcl scripting language. I am using TCL version 8.5. I read text file through tcl script and count similar words frequency. I used for loop and dictionary to count similar words and their frequency but output of the program print like this: alpha 4 beta 2 gamma 1 delta 1
But I want to print it in one column each key, value pair of dictionary or we could say each key, value pair print line by line in output. Following is my script in tcl and its output at the end.
set f [open input.txt]
set text [read $f]
foreach word [split $text] {
dict incr words $word
}
puts $words
Output of the above script:
alpha 4 beta 2 gamma 1 delta 1
You would do:
dict for {key value} $words {
puts "$key $value"
}
When reading the dict documentation, take care about which subcommands require a dictionaryVariable (like dict incr) and which require a dictionaryValue (like dict for)
For nice formatting, as suggested by Donal, here's a very terse method:
set maxWid [tcl::mathfunc::max {*}[lmap w [dict keys $words] {string length $w}]]
dict for {word count} $words {puts [format "%-*s = %s" $maxWid $word $count]}
Or, look at the source code for the parray command for further inspiration:
parray tcl_platform ;# to load the proc
info body parray

passing a list as an argument in TCL

I tried create a procedure that gets a list from the user and prints the min and max values of the list.
I think that the problem is passing the list as an argument to the procedure.
Here is my code:
proc minmaxlist {mylist} {
lsort -integer $mylist
puts "my list is: $mylist\n"
#puts "the length is $argc\n"
set min [lindex $mylist 0]
set max [lindex $mylist [llength[mylist] -1]]
puts "max is $max"
puts "min is $min"
}
set mylist [list $argv]
minmaxlist $mylist
I found that if my list is {5 7 0} my list[0] gets the value of 5 7 0 instead of 5.
Thanks!
The problem is not passing the list to the procedure, but the [list $argv]. The argv variable already contains a list. By wrapping it in another list command, you end up with a list with only one element (which itself is another list). That single element will then end up to be both the minimum and maximum value. So, just pass $argv to the proc, or set mylist to $argv, instead of [list $argv].
Then in your proc, you sort the list and discard the result. You will want to store the result in a variable. You can reuse mylist for that: set mylist [lsort -integer $mylist].
You may also have noticed that your statement to get the max value doesn't work. You probably meant to do set max [lindex $mylist [expr {[llength $mylist] - 1}]]. The last element can more easily be obtained via set max [lindex $mylist end]

tcl set list of arrays produce duplicates

I'm producing a TCL procedure that will return a list of arrays of devices under a switch. The definition is an XML file that is read. The resulting lists of XML entries are parsed using a recursive procedure and the device attributes are placed in an array.
Each array is then placed in a list and reflected back to the caller. My problem is that when I print out the list of devices, the last device added to the list is printed out each time. The contents of the list is all duplicates.
Note: I'm using the excellent proc, 'xml2list' that was found here. I'm sorry, I forgot who submitted this.
The following code illustrates the problem:
source C:/src/tcl/xml2list.tcl
# Read and parse XML file
set fh [open C:/data/tcl/testfile.xml r]
set myxml [read $fh]
set mylist [xml2list $myxml]
array set mydevice {}
proc devicesByName { name thelist list_to_fill} {
global mydevice
global set found_sw 0
upvar $list_to_fill device_arr
foreach switch [lindex $thelist 2] {
set atts [lindex $switch 1]
if { [lindex $switch 0] == "Switch" } {
if { $name == [lindex $atts 3] } {
set found_sw 1
puts "==== Found Switch: $name ===="
} else {
set found_sw 0
}
} elseif { $found_sw == 1 && [string length [lindex $atts 3]] > 0 } {
set mydevice(hdr) [lindex $switch 0]
set mydevice(port) [lindex $atts 1]
set mydevice(name) [lindex $atts 3]
set mydevice(type) [lindex $atts 5]
puts "Device Found: $mydevice(name)"
set text [lindex $switch 2]
set mydevice(ip) [lindex [lindex $text 0] 1]
lappend device_arr mydevice
}
devicesByName $name $switch device_arr
}
}
#--- Call proc here
# set a local array var and send to the proc
set device_arr {}
devicesByName "Switch1" $mylist device_arr
# read out the contents of the list of arrays
for {set i 0} {$i<[llength $device_arr]} {incr i} {
upvar #0 [lindex $device_arr $i] temp
if {[array exists temp]} {
puts "\[$i\] Device: $temp(name)-$temp(ip)"
}
}
The XML file is here:
<Topology>
<Switch ports="48" name="Switch1" ip="10.1.1.3">
<Device port="1" name="RHEL53-Complete1" type="host">10.1.1.10</Device>
<Device port="2" name="Windows-Complete1" type="host">10.1.2.11</Device>
<Device port="3" name="Solaris-Complete1" type="host">10.1.2.12</Device>
</Switch>
<Switch ports="36" name="Switch2" ip="10.1.1.4">
<Device port="1" name="Windows-Complete2" type="host">10.1.3.10</Device>
</Switch>
<Router ports="24" name="Router1" ip="10.1.1.2">
<Device port="1" name="Switch1" type="switch">10.1.1.3</Device>
<Device port="2" name="Switch2" type="switch">10.1.1.4</Device>
</Router>
</Topology>
If my code blocks look bad, please excuse that. I followed the directions as I read them, but it didn't look correct. I could not fix it, so just posted anyway.
Thanks in advance...
Arrays in tcl are not values. Therefore they don't behave like regular variables. They are in fact something special like filehandles or sockets.
You cannot assign an array to a list like that. Doing:
lappend device_arr mydevice
simply appends the string "mydevice" to the list device_arr. That string happens to be the name of a global variable so that string may be used later to access that global variable.
To build up a key-value data structure what you want is a dict. You can think of a dict as a special list that has even numbers of elements in the format: {key value key value}. In fact, this data structure works even on very old versions of tcl before the introduction of dicts because the foreach loop in tcl can be used to process key-value pairs.
So what you want is to create a new $mydevice dict each loop and use [dict set] to assign the values.
Alternatively you can keep most of your code and change your lappend to:
lappend device_arr [array get mydevice]
This works because [array get] returns a key-value list which can be treated as a dict. You can later access the data using the dict command.
Array variables can't be used as values. To put the contents of one into a list element, send it to a proc, write it to a file etc, convert it to list form (key, value, key, value...) with array get.
lappend device_arr [array get mydevice]
To use it later, write the list back to an array with array set.
foreach device_l $device_arr {
#array unset device
array set device $device_l
puts "$device(name)-$device(ip)"
}
Note that array set doesn't erase the old keys in the destination array, so if you use it in a loop and the key names aren't always the same, you need to clear the array every iteration.
You can store this information in two ways using arrays . First is as a multi-dimensional array, in this case a three dimensional array and the second is a one dimensional array storing a list that can be converted easily to an array later for accessing data at a later time.
For the 3d array the key would be Switch Name,device_port,dataname you would change your erroneous temporary myDevice and lappend code to
# attr is a list of { attributename1 value1 ... attributenameN valueN}
array set temp $attr
set port $temp(port)
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
# name already set to "Switch1" etc
foreach f [array names temp ] {
set device_arr($name,$port,$f) $temp($f)
}
set device_arr($name,$port,ip) $ip
array unset temp
this code results in the following ( when parray device_arr
parray device_arr
device_arr(Switch1,1,name) "Switch1"
device_arr(Switch1,1,port) 1
device_arr(Switch1,1,type) "RedHat .."
device_arr(Switch1,1,ip) 10..
device_arr(Switch1,2,name) "Switch1"
device_arr(Switch1,2,port) 1
device_arr(Switch1,2,type) "RedHat .."
device_arr(Switch1,2,ip) 10..
...
device_arr(Switch2,1,name) "Switch1"
device_arr(Switch2,1,port) 1
device_arr(Switch2,1,type) "Windows Complete"
device_arr(Switch2,1,ip) 10..
....
to find ip of Switch1 port2 you would:
puts "the ip of Switch1 port 2 is $device_arr(Switch1,2,ip)"
Note lots of data duplication but you can access all data directly without having to go to an intermediate step to get to the data as in the next scheme
# attr is a list of { attributename1 value1 ... attributenameN valueN}
set data $attr
array set temp $attr
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
lappend data ip $ip
set key "$name,$temp(port)"
# name already set to "Switch1" etc
set device_arr($name,$port) $data
array unset temp
doing a parray device_arr gives:
device_arr(Switch1,1) { port "1" name "RHEL53-Complete1" type "host" ip 10.1.1.10 }
device_arr(Switch1,2) { port "2" name "Windows-Complete1" type "host" ip 10.1.2.11}
....
to find the ip of swtich1 port 2 you would
array set temp $device_array(Switch1,2)
puts "ip of device 2 is $temp(ip)"