creating and appending an existing array in tcl - arrays

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.

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

Not able to divide each row of a csv file in the form of array using perl

I am stucked in a problem wherein I am parsing a csv file. The CSV file looks like-
CPU Name,DISABLE,Memory,Encoding,Extra Encoding
,b,d,,
String1,YES,1TB,Enabled,Enabled
String2,NO,1TB,Enabled,Enabled
String3,YES,1TB,Enabled,Enabled
I want to capture the first two rows in two different arrays. The code that I am using to do it is-
my $row_no =0;
while(my $row=<$fi>){
chomp($row);
$row=~ s/\A\s+//g;
$row=~s/\R//g;
#say $row;
if($row_no==0)
{
#say $row;
my #name_initial = split(',',$row);
say length(#name_initial);
say #name_initial;
}
elsif($row_no==1)
{
#say $row;
#data_type_initial =split(',',$row);
say length(#data_type_initial);
say #data_type_initial;
}
$row_no++;
}
Now I formed two arrays from topmost two lines in file (#name_initial and #data_type_initial respectively).When I am printing these array I can see all the 5 values but when I am printing the length of array it is showing length of each array as 1. When I am printing the element using index of arrays I find each element in place then why it is showing length as 1. Also second array which is formed from second line of csv file is printed as "bd". All the null values are gone and although it is containing two values 'b' and 'd'. Its length is printed as 1.
I want to convert the row of csv file in array with all the null and non_NULL values so that I can iterate on the array elements and can give conditions based on null and non null values.How can I do that???
Have a look at perldoc length. It says this:
length EXPR
length
Returns the length in characters of the value of
EXPR. If EXPR is omitted, returns the length of $_. If EXPR is
undefined, returns undef.
This function cannot be used on an entire array or hash to find out
how many elements these have. For that, use scalar #array and scalar
keys %hash, respectively.
Like all Perl character operations, length normally deals in logical
characters, not physical bytes. For how many bytes a string encoded as
UTF-8 would take up, use length(Encode::encode('UTF-8', EXPR)) (you'll
have to use Encode first). See Encode and perlunicode.
In particular, the bit that says "This function cannot be used on an entire array or hash to find out how many elements these have. For that, use scalar #array and scalar keys %hash, respectively".
So you're using the wrong approach here. Instead of say length(#array), you need say scalar(#array).
To explain the results you're getting. length() expects to be given a scalar value (a string) to measure. So it treats your array as a scalar (effectively adding an invisible call to scalar()) and gets back the number of elements in the array (which is "5") and length() then tells you the number of elements in that string - which is 1.
It's also worth pointing out that you don't need to keep track of your own $row_no variable. Perl has a built-in variable called $. which contains the current record number.
Using that knowledge (and adding little whitespace) gives us something like this:
while (my $row = <$fi>) {
chomp($row);
$row =~ s/\A\s+//g;
$row =~s/\R//g;
#say $row;
if ($. == 0) {
#say $row;
my #name_initial = split(/,/, $row);
say scalar(#name_initial);
say #name_initial;
} elsif ($. == 1) {
#say $row;
#data_type_initial = split(/,/, $row);
say scalar(#data_type_initial);
say #data_type_initial;
}
}
Update: You sneaked a couple of extra questions in at the end of this one. I'd suggest that you raise those separately.

How to create two dimensional array with 2 arrays

How to create multiple dimentional array with 2 arrays?
#param=("param1","param2","param3");
#value=("value1_1, value1_2, value1_3", "value2_1, value2_2, value2_3","value3_1, value3_2, value3_3");
Output:
#out=(["param1"]["value1_1", "value1_2", "value1_3"], ["param2"]["value2_1", "value2_2", "value2_3"], ["param3"]["value3_1", "value3_2", "value3_3"])
I've tried this way:
$j=0;
foreach $i(#param1){
push #{$out[$i]}, split(", ", $value[$j]);
$j++;}
It's not exactly clear to me what data structure you want to create.
However, I assume you are trying to create a hash of arrays (a hash table is also known as dictionary or associative array), not an array.
The difference in Perl is that an array always uses integers as keys, whereas a hash always uses strings.
The resulting data structure would then look like
%out = (
'param1' => ['value1_1', 'value1_2', 'value1_3'],
'param2' => ['value2_1', 'value2_2', 'value2_3'],
'param3' => ['value3_1', 'value3_2', 'value3_3'],
);
We can create this data structure like so:
my %out;
for my $i (0 .. $#param) {
$out{$param[$i]} = [split /,\s*/, $value[$i]];
}
Note that $#foo is the highest index in the #foo array. Therefore, 0 .. $#foo would be the range of all indices in #foo. Also note that entries in hashes are accessed with a curly brace subscript $hash{$key}, unlike arrays which use square brackets $array[$index].
You can access multiple elements in a hash or array at the same time by using a slice – #foo{'a', 'b', 'c'} is equivalent to ($foo{a}, $foo{b}, $foo{c}). We can also transform a list of elements by using the map {BLOCK} LIST function. Together, this allows for the following solution:
my %out;
#out{#param} = map { [split /,\s*/, $_] } #value;
Inside the map block, the $_ variable is set to each item in the input list in turn.
To learn more about complex data structures, read (in this order):
perldoc perreftut
perldoc perllol
perldoc perldsc
You can also read the documentation for the map function and for the foreach loop.

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.

Can BerkeleyDB in perl handle a hash of hashes of hashes (up to n)?

I have a script that utilizes a hash, which contains four strings as keys whose values are hashes. These hashes also contain four strings as keys which also have hashes as their values. This pattern continues up to n-1 levels, which is determined at run-time. The nth-level of hashes contain integer (as opposed to the usual hash-reference) values.
I installed the BerkeleyDB module for Perl so I can use disk space instead of RAM to store this hash. I assumed that I could simply tie the hash to a database, and it would work, so I added the following to my code:
my %tags = () ;
my $file = "db_tags.db" ;
unlink $file;
tie %tags, "BerkeleyDB::Hash",
-Filename => $file,
-Flags => DB_CREATE
or die "Cannot open $file\n" ;
However, I get the error:
Can't use string ("HASH(0x1a69ad8)") as a HASH ref while "strict refs" in use at getUniqSubTreeBDB.pl line 31, line 1.
To test, I created a new script, with the code (above) that tied to hash to a file. Then I added the following:
my $href = \%tags;
$tags{'C'} = {} ;
And it ran fine. Then I added:
$tags{'C'}->{'G'} = {} ;
And it would give pretty much the same error. I am thinking that BerkeleyDB cannot handle the type of data structure I am creating. Maybe it was able to handle the first level (C->{}) in my test because it was just a regular key -> scaler?
Anyways, any suggestions or affirmations of my hypothesis would be appreciated.
Use DBM::Deep.
my $db = DBM::Deep->new( "foo.db" );
$db->{mykey} = "myvalue";
$db->{myhash} = {};
$db->{myhash}->{subkey} = "subvalue";
print $db->{myhash}->{subkey} . "\n";
The code I provided yesterday would work fine with this.
sub get_node {
my $p = \shift;
$p = \( ($$p)->{$_} ) for #_;
return $p;
}
my #seqs = qw( CG CA TT CG );
my $tree = DBM::Deep->new("foo.db");
++${ get_node($tree, split //) } for #seqs;
No. BerkeleyDB stores pairs of one key and one value, where both are arbitrary bytestrings. If you store a hashref as the value, it'll store the string representation of a hashref, which isn't very useful when you read it back (as you noticed).
The MLDBM module can do something like you describe, but it works by serializing the top-level hashref to a string and storing that in the DBM file. This means it has to read/write the entire top-level hashref every time you access or change a value in it.
Depending on your application, you may be able to combine your keys into a single string, and use that as the key for your DBM file. The main limitation with that is that it's difficult to iterate over the keys of one of your interior hashes.
You might use the semi-obsolete multidimensional array emulation for this. $foo{$a,$b,$c} is interpreted as $foo{join($;, $a, $b, $c)}, and that works with tied hashes also.
No; it can only store strings. But you can use the →filter_fetch_value and →filter_store_value to define "filters" that will automatically freeze arbitrary structures to strings before storing, and to convert back when fetching. There are analogous hooks for marshalling and unmarshalling non-string keys.
Beware though: using this method to store objects that share subobjects will not preserve the sharing. For example:
$a = [1, 2, 3];
$g = { array => $a };
$h = { array => $a };
$db{g} = $g;
$db{h} = $h;
#$a = ();
push #{$db{g}{array}}, 4;
print #{$db{g}{array}}; # prints 1234, not 4
print #{$db{h}{array}}; # prints 123, not 1234 or 4
%db here is a tied hash; if it were an ordinary hash the two prints would both print 4.
While you can't store normal multidimensional hashes in a BerkeleyDB tied hash, you can use emulated multidimensional hashes with a syntax like $tags{ 'C', 'G'}. This creates a single key that looks like ('C' . $; . 'G')
I had the same question, found this. Might be useful for you as well.
Storing data structures as values in BDB
Often, we might be interested in storing complex data structures: arrays, hashtables,… whose elements can be simple values, of references to other data structures. To do this, we need to serialize the data structure: convert it to a string that can be stored in the database, and can be later converted back into the original data structure using a deserialization procedure.
There are several perl modules available to perform this serialization/deserialization process. One of the most popular is JSON::XS. The next example shows how to use this module:
use JSON::XS;
# Data to be stored
my %structure;
# Convert the data into a json string
my $json = encode_json(%structure);
# Save it in the database
$dbh->db_put($key,$json);
To retrieve the original structure, we perform the inverse operation:
# Retrieve the json string from the database
$dbh->db_get($key, $json);
# Deserialize the json string into a data structure
my $hr_structure = decode_json($json);
In perl you can do this. You are using references beyond the first level.
use GDBM_File;
use Storable;
use MLDBM qw(GDBM_File Storable);
my %hash;
my %level_2_hash;
my %level_3_hash1 = (key1 => x, key2 => y, key3 => z)
my %level_3_hash2 = (key10 => a, key20 => b, key30 => c)
$level_2_hash = (keyA => /%level_3_hash1, keyB => level_3_hash2)
$hash{key} = \%level_2_hash;
This can be found in the online beginning perl book in chapter 13.

Resources