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
Related
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]
I wrote the following proc, which simulates the filter function in Lodash (javascript library) (https://lodash.com/docs/4.17.4#filter). You can call it in 3.5 basic formats, seen in the examples section. For the latter three calling options I would like to get rid of the the requirement to send in -s (shorthand). In order to do that I need to differentiate between an anonymous proc and a list/dict/string.
I tried looking at string is, but there isn't a string is proc. In researching here: http://wiki.tcl.tk/10166 I found they recommend info complete, however in most cases the parameters would pass that test regardless of the type of parameter.
Does anyone know of a way to reliable test this? I know I could leave it or change the proc definition, but I'm trying to stay as true as possible to Lodash.
Examples:
set users [list \
[dict create user barney age 36 active true] \
[dict create user fred age 40 active false] \
]
1. set result [_filter [list 1 2 3 4] {x {return true}}]
2. set result [_filter $users -s [dict create age 36 active true]]
3. set result [_filter $users -s [list age 36]]
4. set result [_filter $users -s "active"]
Proc Code:
proc _filter {collection predicate args} {
# They want to use shorthand syntax
if {$predicate=="-s"} {
# They passed a list/dict
if {[_dictIs {*}$args]} {
set predicate {x {
upvar args args
set truthy 1
dict for {k v} {*}$args {
if {[dict get $x $k]!=$v} {
set truthy false
break
}
}
return $truthy
}}
# They passed just an individual string
} else {
set predicate {x {
upvar args args;
if {[dict get $x $args]} {
return true;
}
return false;
}}
}
}
# Start the result list and the index (which may not be used)
set result {}
set i -1
# For each item in collection apply the iteratee.
# Dynamically pass the correct parameters.
set paramLen [llength [lindex $predicate 0]]
foreach item $collection {
set param [list $item]
if {$paramLen>=2} {lappend param [incr i];}
if {$paramLen>=3} {lappend param $collection;}
if {[apply $predicate {*}$param]} {
lappend result $item
}
}
return $result
}
Is x {return true} a string, a list, a dictionary or a lambda term (the correct name for an anonymous proc)?
The truth is that it may be all of them; it would be correct to say it was a value that was a member of any of the mentioned types. You need to describe your intent more precisely and explicitly rather than hiding it inside some sort of type magic. That greater precision may be achieved by using an option like -s or by different main command names, but it is still necessary either way. You cannot correctly and safely do what you seek to do.
In a little more depth…
All Tcl values are valid as strings.
Lists have a defined syntax and are properly subtypes of strings. (They're implemented differently internally, but you are supposed to ignore such details.)
Dictionaries have a syntax that is equivalent to lists with even numbers of elements where the elements at the even indices are all unique from each other.
Lambda terms are lists with two or three elements (the third element is the name of the context namespace, and defaults to the global namespace if it is absent). The first element of the list needs to be a valid list as well.
A two-element list matches the requirements for all the above. In Tcl's actual type logic, it is simultaneously all of the above. A particular instantiation of the value might have a particular implementation representation under the covers, but that is a transient thing that does not reflect the true type of the value.
Tcl's type system is different to that of many other languages.
I have a list of cells,
U1864
u_dhm_lut/U4
u_dhm_lut/lut_out_reg_2_
u_dhm_lut/lut_in_reg_2_
And I want to calculate how many times each name comes
Result will:
U1864 1
u_dhm_lut/lut_out_reg_2_ 18
u_dhm_lut/lut_in_reg_2_ 14
u_dhm_lut/U4 10
The code is like:
set cell_cnt [open "demo.txt" r]
set cell [read $cell_cnt]
set b [open "number_of_cell.txt" w+]
proc countwords {cell_count} {
set unique_name [lsort -unique $cell_count]
foreach count $unique_name {
set cnt 0
foreach item $cell_count {
if {$item == $count} {
incr cnt
}
}
puts $b "$count :: $cnt"
}
}
countwords $cell
It says can't read "b":no such variable while executing
"puts $b "$count :: $cnt""
Why am i not able write a file inside proc?
Code inside a procedure scope can't use variables defined outside that scope, e.g. global variables. To be able to use global variables, you can import them into the procedure scope:
proc countwords cell_count {
global b
or use a qualified name:
puts $::b ...
You can also bypass the issue by passing the file handle to the procedure:
proc countwords {b cell_count} {
...
countwords $b $cell
or move the code for opening the file inside the procedure (not recommended: procedures should have one job only).
Old answer, based on the question title
This is one of the most frequently asked frequently asked questions. If you look a while back in the question list, you will find quite a few answers to this.
The solution is actually pretty easy, and the core of it is to use an array as a frequency table, with the words as keys and the frequencies as values. The incr command creates new entries (with a value of one) in the table as needed.
foreach word $words {
incr count($word)
}
The result is similarly easy to check:
parray count
The result can of course also be used in a script in any way that an array can be used.
Documentation:
array,
foreach,
incr,
parray
You can use the open file code i.e "set b [open "number_of_cell.txt" w+]" inside the method. This should also solve your problem
What is the syntax for a proc in tcl which automatically takes arguments if user din't give any arg?
I used something like
proc a {{args 1}} {
puts $args
}
a
when I used this I dint get my args value as 1. It returns a blank string. Please help me with this.
It sounds like you're trying to do two different things together:
Accept a variable number of arguments to your proc
If no arguments are provided, use default value(s)
There isn't really a syntax for proc that handles this case specifically. However, it's fairly easy to accomplish. You can accept a variable number of arguments (args keyword), and then check to see if any were supplied (and use a default value if not).
proc myproc {args} {
if { [llength [info level 0]] < 2 } { #called with no args
set args {the default list of values}
}
# rest of the code goes here
}
The info level 0 command returns the actual command being run as it was called, with arguments. Hence, if the length of it's result is < 2, we know the current command was called with no arguments (since the first element in the list is the actual command name itself).
The word args is a reserved word that has a specific meaning in proc definitions. Use a different variable name:
proc a {{x 1}} {
puts $x
}
a
Additional answer
In a proc definition, default values are given by defining the argument as a list with two members: the first will be the name of the argument, the second is the default value.
The word args is a special argument that implements rest arguments (that is, it captures remaining arguments not specified in the argument list). This is how one can implement variadic functions in Tcl.
The args arguments and default values can be used together when defining procs. But args cannot have default values. Any argument listed before args can have default values. But arguments that have default values must be listed after arguments without default values. So, basically you can write a function like this:
proc a {x {y 1} args} {
puts "$x $y ($args)"
}
a 1 ;# prints 1 1 ()
a 1 2 ;# prints 1 2 ()
a 1 2 3 4 ;# prints 1 2 (3 4)
If your use-case meets this pattern then you can define arguments with default values before args like the example above. If not, then your only option is to process args yourself:
# Assume parameters are key-value pairs,
# if value not given then default to 1
proc b args {
foreach {x y} $args {
if {$y == ""} { set y 1 }
puts "$x -> $y"
}
}
b 1 2 ;# prints 1 -> 2
b 1 2 3 ;# prints 1 -> 2, 3 -> 1
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