accessing array result in procedure using tcl - arrays

am beginner in Tcl/tk, facing problem in accessing array in procedure
below is my problem statement
proc myproc {args} {
set aaa ""
set bbb ""
set ccc ""
foreach "field value" $args {
set $field $value
}
# assigning input args values to an array a
set a(3:0) $aaa
set a(6:4) $bbb
set a(25:7) $ccc
#do some computation on input arguments may be addition
#
#
#
# now the result am trying to fetch into another array b
set $b(word0) $x
set $b(word1) $y
set $b(word2) $z
set $b(word3) $u
return [array get b]
}
now i need to pass arguments to myproc and return array i need to access.
set args_1 "g 1 h 4 k 6"
i tried the below syntax it was throwing me error.
array set a [myproc[array get $args_1]]
can somebody help me in solving this issue
trying to give string as input for procedure myproc
and later trying to do some computation with that input values.
later after all computation got set of string values, which are assigned to array as below
set $b(word0) $x
set $b(word1) $y
set $b(word2) $z
set $b(word3) $u
want to send this array b as return.
example:
proc myproc {} {
set $b(word0) $x
set $b(word1) $y
set $b(word2) $z
set $b(word3) $u
return [array get b]
}
i have tried to access array b as below
array set a [myproc[array get b]]
it worked :) was able to creat new array in calling function.
but, need to pass string arguments to myproc and get return as array

That function looks OK to me. There may be better ways to write it but it's essentially OK as is.
There are however a couple of problems with how you call that function.
First, you're confusing arrays and lists. In tcl, an array is a collection of key-value pairs. Other languages call this a "hash" or "map". A list is what is sounds like: a list of values. Other languages call this "array" or "list".
So, first off:
tcl other languages
--- ---------------
array = hash
list = array
The name "array" was chosen because the concept of a collection of key-value pairs is known in computer science as "associative arrays". Which is a term that predates the use of the word "array" to mean a list of values in languages like C and Java.
So, here you're declaring a list:
set args_1 "g 1 h 4 k 6"
And you're trying to access it as an array:
array get $args_1
Which should throw out an error that says that $args_1 is not an array. Which is in fact true.
So, simply replace it with the list variable:
$args_1
Which gives us:
array set a [myproc$args_1]
This should throw another error which basically says that the function myproc g 1 h 4 k 6 doesn't exist. Yes, in tcl it is valid to have whitespace in a function's name. For example:
proc "an example" {} {return "an example"}
That's valid code. And you call it like this:
set x ["an example"]
So it's no surprise that tcl can't find a function called "myproc g 1 h 4 k 6".
What this means is that whitespace is significant in tcl. You can't do:
set x [y[z]]
That's most likely a syntax error. It should be:
set x [y [z]]
# ^
# |______ whitespace was missing
So your code should be:
array set a [myproc $args_1]

Related

Certain elements in tcl array not accessable (i.e. "no such element in array")

I have declared an array, of which I can access all of the elements through a loop but hen I try to directly access some elements, they do not exist.
I have tried accessing the array through CUI, writing scripts to do so, both in a loop, directly, and by making the key a variable; the behavior is very bizzare.
I have my array tileDirectory declared from before hand and when I enter in the CUI
foreach {a b} [array get tileDirectory] {puts $a}
…
xu/iq/grf/reg23DataReg/[0]
su/pipe/dcacheTag/dcPLRUEntry[31].dcPLRUEntryXReg/[0]
xu/cq/cq00/cqStatusReg/[0]
su/busReq/memIntfc/memReqStDataReg/[96]_nocgc
However if I do:
puts $tileDirectory(su/pipe/dcacheTag/dcPLRUEntry[31].dcPLRUEntryXReg/[0])
can't read "tileDirectory(su/pipe/dcacheTag/dcPLRUEntry[31].dcPLRUEntryXReg/[0])": no such element in array
I would except that I could read the element of the array, as a note the other elements I can read out just fine, and for the sake of pattern, it tends to be the names which have "." in them that give me trouble
Apparently, you have your array defined in multiple places (or in different namespaces) such as the sample program below. For the following program to work, the global td statement must be uncommented.
proc myproc { } {
# global td
set td(abc) 123
puts $td(def)
}
set td(def) 456
puts $td(def)
myproc

How to use an array reference to pairs of elements

I am considering this answer which uses a single array reference of points, where a point is a reference to a two-element array.
My original code of the question (function extract-crossing) uses two separate arrays $x and $y here which I call like this:
my #x = #{ $_[0] }; my #y = #{ $_[1] };
...
return extract_crossing(\#x, \#y);
The new code below based on the answer takes (x, y) and returns a single datatype, here x intercept points:
use strict; use warnings;
use Math::Geometry::Planar qw(SegmentLineIntersection);
use Test::Exception;
sub x_intercepts {
my ($points) = #_;
die 'Must pass at least 2 points' unless #$points >= 2;
my #intercepts;
my #x_axis = ( [0, 0], [1, 0] );
foreach my $i (0 .. $#$points - 1) {
my $intersect = SegmentLineIntersection([#$points[$i,$i+1],#x_axis]);
push #intercepts, $intersect if $intersect;
}
return \#intercepts;
}
which I try to call like this:
my #x = #{ $_[0] }; my #y = #{ $_[1] };
...
my $masi = x_intercepts(\#x);
return $masi;
However, the code does not make sense.
I am confused about passing the "double array" to the x_intercepts() function.
How can you make the example code clearer to the original setting?
If I am understanding the problem here, #ThisSuitIsBlackNot++ has written a function (x_intercepts which is available in the thread: Function to extract intersections of crossing lines on axes) that expects its argument to be a reference to a list of array references. The x_intercepts subroutine in turn uses a function from Math::Geometry::Planar which expects the points of a line segment to be passed as series of array references/anonymous arrays that contain the x,y values for each point.
Again - it is not entirely clear - but it seems your data is in two different arrays: one containing all the x values and one with the corresponding y values. Is this the case? If this is not correct please leave a comment and I will remove this answer.
If that is the source of your problem then you can "munge" or transform your data before you pass it to x_intercepts or - as #ThisSuitIsBlackNot suggests - you can rewrite the function. Here is an example of munging your existing data into an "#input_list" to pass to x_intercepts.
my #xs = qw/-1 1 3/;
my #ys = qw/-1 1 -1 /;
my #input_list ;
foreach my $i ( 0..$#ys ) {
push #input_list, [ $xs[$i], $ys[$i] ] ;
}
my $intercept_list = x_intercepts(\#input_list) ;
say join ",", #$_ for #$intercept_list ;
Adding the lines above to your script produces:
Output:
0,0
2,0
You have to be very careful doing this kind of thing and using tests to make sure you are passing the correctly transformed data in an expected way is a good idea.
I think a more general difficulty is that until you are familiar with perl it is sometimes tricky to easily see what sorts of values a subroutine is expecting, where they end up after they are passed in, and how to access them.
A solid grasp of perl data structures can help with that - for example I think what you are calling a "double array" or "double element" here is an "array of arrays". There are ways to make it easier to see where default arguments passed to a subroutine (in #_) are going (notice how #ThisSuitIsBlackNot has passed them to a nicely named array reference: "($points)"). Copious re-reading of perldocperbsub can help things seem more obvious.
References are key to understanding perl subroutines since to pass an array or hash to a subrouting you need to do so by references. If the argument passed x_intercepts is a list of two lists of anonymous arrays then when it is assigned to ($points), #$points->[0] #$points->[1] will be the arrays contain those lists.
I hope this helps and is not too basic (or incorrect). If #ThisSuitIsBlackNot finds the time to provide an answer you should accept it: some very useful examples have been provided.

For loop to take the value of the whole array each time

Suppose I have 3 arrays, A, B and C
I want to do the following:
A=("1" "2")
B=("3" "4")
C=("5" "6")
for i in $A $B $C; do
echo ${i[0]} ${i[1]}
#process data etc
done
So, basically i takes the value of the whole array each time and I am able to access the specific data stored in each array.
On the 1st loop, i should take the value of the 1st array, A, on the 2nd loop the value of array B etc.
The above code just iterates with i taking the value of the first element of each array, which clearly isn't what I want to achieve.
So the code only outputs 1, 3 and 5.
You can do this in a fully safe and supportable way, but only in bash 4.3 (which adds namevar support), a feature ported over from ksh:
for array_name in A B C; do
declare -n current_array=$array_name
echo "${current_array[0]}" "${current_array[1]}"
done
That said, there's hackery available elsewhere. For instance, you can use eval (allowing a malicious variable name to execute arbitrary code, but otherwise safe):
for array_name in A B C; do
eval 'current_array=( "${'"$array_name"'[#]}"'
echo "${current_array[0]}" "${current_array[1]}"
done
If the elements of the arrays don't contain spaces or wildcard characters, as in your question, you can do:
for i in "${A[*]}" "${B[*]}" "${C[*]}"
do
iarray=($i)
echo ${iarray[0]} ${iarray[1]}
# process data etc
done
"${A[*]}" expands to a single string containing all the elements of ${A[*]}. Then iarray=($i) splits this on whitespace, turning the string back into an array.

creating and appending an existing array in tcl

Hi all i need some help on tcl arrays. I have two procedures in tcl something like below :
proc A {} {
set lst_A [list];
if {true} {
lappend lst_A [::B $file1];
} else {
foreach id $allId {
lappend lst_A [::B $id];
}
}
return $lst_A;
}
proc B {fileID} {
set fileName [::getFileName $fileID]; # getFileName returns filename from fileid
set tcName "[set ItemName]_[set ItemId]";
array set tcArrayName {$fileName $tcSpec};
return $tcArrayName;
}
Now i want to create an array which will be a key-value pair where key is some kind of file id and value is some name associated with that id. Now the thing is in proc A if condition is true i want to create array with one key-value pair only and then append that array to lst_A which in this case will contain only one item i.e. the array returned. But if the condition is false than i loop through some ids and for each id i call proc B and create array which is then appended to lst_A which in this case will contain multiple key-value paired arrays.
So I wrote the above two procedures and created array after reading about arrays in tcl tutorial. But not sure if this is correct way or the most optimised way.
My ultimate goal is to create a lst_A or i would say should be an array which will either contain just one key-value pair if condition is true or else will be an array with multiple key-value pairs. Since i am creating the array in proc B, i could only think of returning the key-value pair from proc B as an array and then append that array to a list i.e. lst_A in proc A.
Any suggestions???
You're confusing array's with lists here.
In tcl, you cannot pass arrays into or return them from functions. Also, tcl uses the term "arrays" for what C++ calls "maps" or Perl and Ruby call "hashes" or what Javascript calls "objects". The term comes from the fact that all these things are generically known as "associative arrays" in computer science. So "arrays" in tcl are key-value pairs, not sequences of data.
Arrays don't normally degenerate into values (or what a seasoned tcl programmer would call "strings"). Therefore this:
array set tcArrayName {$fileName $tcSpec};
return $tcArrayName;
generates a syntax error:
can't read "tcArrayName": variable is array
You can actually degenerate the contents of an array into a value that you can then return. But you have to do it manually via the [array get] command:
return [array get tcArrayName]
The above command will return the content of the tcArrayName array in the form of a two element list:
"$fileName $tcSpec"
which also happens to be a string. And that's the actual string: the character '$' followed by the characters 'f', 'i', 'l', 'e', 'N', 'a', 'm', 'e' etc. Not the filename and the concatenated item name and item id. The literal string "$fileName $tcSpec".
That's because you've used the {} grouping in this line of code:
array set tcArrayName {$fileName $tcSpec}
In tcl, {xxx} behaves the same way 'xxx' does in Perl. Basically its a literal string that does not get substituted. If you want tcl to do $ substitution then you need to use "" grouping:
array set tcArrayName "$fileName $tcSpec"
But this is fragile because if fileName contains spaces then it would break the code. A more robust way to do this is:
array set tcArrayName [list $fileName $tcSpec]
But using an array in this case is a bit redundant since all you're doing is initialising it with a key-value list (also known as a dict in more modern versions of tcl. BTW, what version of tcl are you using?) and then immediately discarding it and returning it's content as a key-value list.
Why not simply return that key-value list:
proc B {fileID} {
set fileName [::getFileName $fileID]; # getFileName returns filename from fileid
set tcName "[set ItemName]_[set ItemId]"
return [list $fileName $tcSpec]
}
But we're still not in the clear. If you try to execute B you'll see this error:
can't read "ItemName": no such variable
That's because tcl does not import global variables into functions by default. As such the variables ItemName and ItemId don't exist in B. You either need to import it explicitly via the global command:
proc B {fileID} {
global ItemName
global ItemId
# ...
}
or access it via the global namespace using a fully qualified variable name:
set tcName "[set ::ItemName]_[set ::ItemId]"
So B should look like this:
proc B {fileID} {
set fileName [::getFileName $fileID]; # getFileName returns filename from fileid
set tcName "[set ::ItemName]_[set ::ItemId]"
return [list $fileName $tcSpec]
}
That's B taken care of but A has some problems as well:
can't read "file1": no such variable
We need to modify it a bit to access the variable file1:
proc A {} {
set lst_A [list];
if {true} { ;# assuming that there will be a valid condition here
lappend lst_A [::B $::file1];
} else {
foreach id $allId {
lappend lst_A [::B $id];
}
}
return $lst_A;
}
This should work as you expect.
If you want a little speed up when you use the key-value list then you should really read up on dicts. As is, dicts won't affect the code given above but it does affect the code calling A. If you plan on using dicts then you can replace the lappend call above with dict append. But older versions of tcl don't have dicts.

TCL: how to return an array?

please find below a snippet of code that passes an array, manipulates the array, but i cannot return the new version of array.
here is the snippet :
proc get_mroute_active { &multicast } {
upvar ${&multicast} MULTICAST ;
set group -1 ;
set src -1 ;
set mcast_group_source_id -1 ;
set MULTICAST($mcast_group_source_id,id) $mcast_group_source_id ;
set MULTICAST($mcast_group_source_id,mcast_group) $group ;
set MULTICAST($mcast_group_source_id,mcast_source) $src ;
puts [array size MULTICAST] ;
parray MULTICAST ;
}
array set multicast { } ;
get_mroute_active [array get multicast] ;
puts [array size multicast] ;
parray multicast ;
And the output of the code is :
3
MULTICAST(-1,id) = -1
MULTICAST(-1,mcast_group) = -1
MULTICAST(-1,mcast_source) = -1
0
Could you please help show me how the "MULTICAST" variable can be assigned to "multicast" ?
The short answer is: you can't return an array from a procedure as arrays are not values — they are peculiar named collections of named values.
There are several ways to deal with this:
The usual approach is to pass arrays by names and make the called procedure modify the array in place. For instance, the code
proc foo {arrayName} {
upvar 1 $arrayName ary
incr ary(one)
}
set x(one) 1
foo x
puts $x(one)
will print "2" as the procedure foo modified a specific value in the specified array in the caller's scope.
Notice how the caller passed the name of an array, "x", instead of "its value" (as you cannot extract a value from an array; but see below) and then the called procedure bound a local variable to that array by its name using the upvar command.
The other approach is to employ array get and array set commands to extract keys and values from arrays and populate arrays with keys and values, respectively. For instance:
set x(one) 1
set x(two) 2
array set another [array get x]
parray another
would print
another(one) = 1
another(two) = 2
The array get command, given an array, returns a flat list with its keys and their respective values interleaved. This way you can return the contents of an array from a procedure and then make the caller do whatever it wishes with these contents, for instance, use the array set command to populate another array in its scope (possibly the same which has been passed to that procedure in the first place).
Note that array set has merge semantics: it does not empty the target array before inserting/replacing its values using the source list.
The third (and may be the best) approach is to use dictionaries which are key/value maps as arrays do but are themselves values, so they can be passed around freely as other values. This requires Tcl 8.5 or later (in Tcl 8.4, you can use a backport of this code in the form of a package. With dicts you get it like this:
proc foo d {
dict set d one 2
return $d
}
set x [dict create]
dict set x one 1
set y [foo $x]
puts $y
which would print "one 2" as the procedure modified the original dictionary then returned it and then the caller assigned it to another variable.

Resources