perl array not populating, despite hours of tinkering - arrays

Um...I have the following code snippet, and was wondering why the second subroutine, search($$) fails to yield results...while the first routine, search_item($$$), performs admirably (imo).
########################
# generate and return a list of users which match only
# one criteria (eg: eyes=2)
#
# $users is a reference to an array of 6-digit hexidecimal user IDs (eg: 000001, 000002, etc)
# $name is the name of the key (or field) to find (eg: 'eyes')
# $value (eg: 2) is compared with the value stored in the key
# if $value matches what's in the $name'd key, then add the uid to a list
sub search_item($$$) {
my ($users, $name, $value) = #_;
my #searched;
foreach my $uid (#$users) {
my %ustats = user::getUserStats($uid);
if ($ustats{$name} eq $value) { push #searched, $uid; }
}
return #searched;
}
########################
# generate and return a list of users which match
# many criteria (eg: eyes=2, hair=1, etc)
#
# $users is a reference to an array of user IDs (eg: 000001, 000002, etc)
# $terms is a reference to an array of search terms (eg: $terms[0] = "eyes=2";)
sub search($$) {
my $users = $_[0]; # an array reference
my $terms = $_[1]; # an array reference
my #searched;
my $first = 1;
foreach my $term (#$terms) {
# since #$terms is an array of scalars, in the format of 'name=value' pairs
my $name = $term; $name =~ s/=(.)*//;
my $value = $term; $value =~ s/$name=//;
if ($first) {
# search the given list reference ($users)
#searched = search_item($users, $name, $value);
$first = 0; # set to 0 cause now we gotta use #searched
} else {
# otherwise use a reference to #searched
#searched = search_item(\#searched, $name, $value);
}
}
return #searched;
}
i have setup the data so the code should return 1 hit. the data is correct and underlying functions (eg: getUserStats($)) also perform flawlessly.
both user 000001 and 969696 have eyes=2 all others eyes=1 and
user ID 000001 is gender=1, all others gender=0
so...if i write:
my #users = getUsers();
foreach my $uid (search_item(\#users, 'eyes', 2)) {
print "$uid<br>\n";
}
i get 2 hits of a total of 6 users in my database (this IS a correct result, of course). satisfied with those results, I run the search routine.
my #terms = ('eyes=2', 'gender=1'); # gender=0 is a boy. 1 is a girl
my #sResults = search(\#users, \#terms);
if (#sResults) {
foreach my $uid (#sResults) {
print "$uid<br>\n";
}
} else {
print "nothing found!<br>\n";
}
i always see "nothing found!" when i pray and hope to see "000001" instead... :(
this seems like legit code....so....whud am i doin wrong guys??? am i not derefencing something correctly? or...is the dereferencing / referencing the source of my dilema? i loath pointers...however incredibly useful :p

Your code does actually work if you pass correct parameters. My best guess is that the strings like eyes=2 that you're using contain spurious whitespace such as a trailing newline
Here's the test program that I used to work on your subrouitines
use strict;
use warnings;
use 5.010;
my %users = (
'000001' => { eyes => 2, gender => 1 },
'000002' => { eyes => 1, gender => 0 },
'000003' => { eyes => 1, gender => 0 },
'000004' => { eyes => 1, gender => 0 },
'969696' => { eyes => 2, gender => 0 },
);
sub user::getUserStats {
my ( $uid ) = #_;
%{ $users{$uid} };
}
########################
# generate and return a list of users which match only
# one criteria (eg: eyes=2)
#
# $users is a reference to an array of 6-digit hexidecimal user IDs (eg: 000001, 000002, etc)
# $name is the name of the key (or field) to find (eg: 'eyes')
# $value (eg: 2) is compared with the value stored in the key
# if $value matches what's in the $name'd key, then add the uid to a list
sub search_item($$$) {
my ( $users, $name, $value ) = #_;
my #searched;
foreach my $uid ( #$users ) {
my %ustats = user::getUserStats( $uid );
if ( $ustats{$name} eq $value ) { push #searched, $uid; }
}
return #searched;
}
########################
# generate and return a list of users which match
# many criteria (eg: eyes=2, hair=1, etc)
#
# $users is a reference to an array of user IDs (eg: 000001, 000002, etc)
# $terms is a reference to an array of search terms (eg: $terms[0] = "eyes=2";)
sub search($$) {
my $users = $_[0]; # an array reference
my $terms = $_[1]; # an array reference
my #searched;
my $first = 1;
foreach my $term ( #$terms ) {
# since #$terms is an array of scalars, in the format of 'name=value' pairs
my $name = $term;
$name =~ s/=(.)*//;
my $value = $term;
$value =~ s/$name=//;
if ( $first ) {
# search the given list reference ($users)
#searched = search_item( $users, $name, $value );
$first = 0; # set to 0 cause now we gotta use #searched
}
else {
# otherwise use a reference to #searched
#searched = search_item( \#searched, $name, $value );
}
}
return #searched;
}
my $users = [ keys %users ];
say for search( $users, [ 'eyes=2', 'gender=1' ] );
output
000001
Here's how I would write similar subroutines that behave identically and take the same parameters, but there is a lot in the design of this application that is less that optimal
sub search_item {
my ( $users, $name, $value ) = #_;
grep {
my %ustats = user::getUserStats( $_ );
$ustats{$name} eq $value;
} #$users;
}
sub search {
my ($users, $terms) = #_;
my #searched;
for my $term ( #$terms ) {
my ($name, $value) = split /=/, $term;
#searched = search_item( $users, $name, $value );
$users = \#searched;
}
#searched;
}
but I think user::getUserStats should be called User::get_user_stats (because Perl reserves capital letters for global identifiers such as package names) and it should return a reference to a hash instead of just a list

Related

Print a Perl hash of hashes

I am building a hash of hashes. How can I use while to get each value?
my %users = ();
while ( my $row_ref = $select_sth->fetchrow_hashref() ) {
$id = $row_ref->{uid};
$name = $row_ref->{name};
my $loc = $row_ref->{loc };
$users{$loc}{$name} = $id;
}
while ( my ($user, $val) = each(%users) ) {
# how to get name, loc, id?
my $uname = ??
my $uloc = ??
my $uid = ??
}
Iterate over the two levels of the hash in nested loops.
for my $loc (keys %users) {
for my $name (keys $users{$loc}->%*) {
say "loc: $loc -- name: $name -- id: $users{$loc}{$name}";
}
}
Hint: use Data::Dumper or similar to visualise a nested data structure.

Clean way to access a nested data structure

I have a segment of code that, although it works, does not look like a clean way to do things.
I build the structure using:
foreach my $n (#node_list)
{
chomp ($n);
foreach my $c (#cpes)
{
my #returned; #Interfaces to CPEs with MED settings
my #creturned; #General Customer Interfaces
my ($cust) = $c =~ /([a-zA-Z]+)[_-][a-zA-Z0-9]+/s;
print "\n\t\tCustomer is $cust\n";
chomp($c);
$c = uc $c;
my ($search) = $c;
(#returned) = `cat /curr/$n | grep "$search"`;
if (#returned)
{
my $cust_match = 'interface \"' . $cust;
(#creturned) = `cat /curr/$n | egrep -i "$cust_match" | grep -v "$search"`;
}
if (#creturned) #Have we found other CPEs on the same router
{
my ($nf) = $n =~ /([a-zA-Z0-9-]+).cfg/s;
my (#interfaces) = map { /([A-Z0-9_]+)/s } #creturned;
#interfaces = uniq(#interfaces);
unshift (#interfaces, $c);
push (#new_out, {$nf => {$cust => [#interfaces]}});
}
}
This will return:
$VAR1 = [
{
'router-xx-xx' => {
'50000' => [
[
'THXXXXVF_NLXXXX40_1121_2',
'10x.xx.x.50'
],
[
'THXXXPVF_NLXXXX66_1121_1',
'10x.xx.x.70'
],
[
'THXXXXVF_NLXXXX67_1121_2',
'10x.xx.x.78'
],
}
},
Each router can have a number of VPRNs and each VPRN can contain multiple interfaces. In the example above I've shown one router with one VPRN.
However, when it comes to accessing elements in the above, I've written the following convoluted (but working) code:
foreach my $candidate (#nodes)
{
my %node = %{ $candidate };
foreach my $n (keys %node)
{
print "\nRouter is $n\n";
foreach my $cust (keys %{ $node{$n} })
{
print "Customer on $n is \n" . Dumper $cust;
my #intlist = #{$node{$n}{$cust}};
my $med_cpe = $intlist[0]; #the CPE that was used to find node
{truncated}
}
}
}
}
You don't explain exactly what you find "convoluted" about the traversal code, but you have made it unnecessarily complex by duplicating data into #intlist and %node. The excessive and inconsistent indentation also makes it ungainly
I would write something closer to this
for my $node ( #nodes ) {
for my $n ( keys %$node ) {
print "\nRouter is $n\n";
for my $cust ( keys %{ $node->{$n} } ) {
print "Customer on $n is \n" . Dumper \$cust;
my $med_cpe = $node->{$n}{$cust}[0];
}
}
}
If you don't need the values of $node and $n except to access $med_cpe then you don't need a nested data structure at all: a simple array is fine. On the face of it, an array like this will do what you need
[
[
'router-xx-xx',
'50000',
'THXXXXVF_NLXXXX40_1121_2',
'10x.xx.x.50',
],
[
'router-xx-xx',
'50000',
'THXXXPVF_NLXXXX66_1121_1',
'10x.xx.x.70',
],
...
]

perl hash with array

I did same hash like this:
my %tags_hash;
Then I iterate some map and add value into #tags_hash:
if (#tagslist) {
for (my $i = 0; $i <= $#tagslist; $i++) {
my %tag = %{$tagslist[$i]};
$tags_hash{$tag{'refid'}} = $tag{'name'};
}}
But I would like to have has with array, so when key exists then add value to array.
Something like this:
e.g. of iterations
1,
key = 1
value = "good"
{1:['good']}
2,
key = 1
value = "bad"
{1:['good', 'bad']}
3,
key = 2
value = "bad"
{1:['good', 'bad'], 2:['bad']}
And then I want to get array from the key:
print $tags_hash{'1'};
Returns: ['good', 'bad']
An extended example:
#!/usr/bin/perl
use strict;
use warnings;
my $hash = {}; # hash ref
#populate hash
push #{ $hash->{1} }, 'good';
push #{ $hash->{1} }, 'bad';
push #{ $hash->{2} }, 'bad';
my #keys = keys %{ $hash }; # get hash keys
foreach my $key (#keys) { # crawl through hash
print "$key: ";
my #list = #{$hash->{$key}}; # get list associate within this key
foreach my $item (#list) { # iterate through items
print "$item ";
}
print "\n";
}
output:
1: good bad
2: bad
So the value of the hash element to be an array ref. Once you have that, all you need to do is push the value onto the array.
$hash{$key} //= [];
push #{ $hash{$key} }, $val;
Or the following:
push #{ $hash{$key} //= [] }, $val;
Or, thanks to autovivification, just the following:
push #{ $hash{$key} }, $val;
For example,
for (
[ 1, 'good' ],
[ 1, 'bad' ],
[ 2, 'bad' ],
) {
my ($key, $val) = #$_;
push #{ $hash{$key} }, $val;
}

Iterate through a hash and an array in Perl

I have an array and a hash:
#arraycodons = "AATG", "AAAA", "TTGC"... etc.
%hashdictionary = ("AATG" => "A", "AAAA" => "B"... etc.)
I need to translate each element of the array for the corresponding value in hashdictionary. However, I obtain a wrong translation.....
To see the problem, I have printed $codon (each element of the array), but each codon is repeated several times..... and It shouldn't.
sub translation() {
foreach $codon (#arraycodons) {
foreach $k (keys %hashdictionary) {
if ($codon == $k) {
$v = $hashdictionary{$k};
print $codon;
}
}
}
}
I don't know if I've explained my problem well enough, but I can't go on with my code if this doesn't work...
Many thanks in advance.
You appear to be looping through the keys of your hash (also known as a "dictionary") to find your desired key. This defeats the purpose of a hash (also known as a "dictionary") - the primary advantage of which is ultra fast lookups of a key.
Try, instead of
foreach $codon (#arraycodons) {
foreach $k (keys %hashdictionary) {
if ($codon == $k) {
$v = $hashdictionary{$k};
print $codon;
}
}
}
this:
foreach $codon (#arraycodons) {
my $value = $hashdictionary{$codon};
print( "$codon => $value\n" );
}
or:
foreach my $key ( keys %hashdictionary ) {
my $value = $hashdictionary{$key};
print( "$key => $value\n" );
}
my #mappedcodons = map {$hashdictionary{$_}}
grep (defined $hashdictionary{$_},#arraycodons);
or
my #mappedcodons = grep ($_ ne "", map{$hashdictionary{$_} || ""} #arraycodons);
my #words = ("car", "house", "world");
my %dictionary = ("car" => "el coche", "house" => "la casa", "world" => "el mundo");
my #keys = keys %dictionary;
foreach(#words) {
my $word = $_;
foreach(#keys) {
if($_ eq $word) { # eq, not ==
my $translation = $dictionary{$_};
print "The Spanish translation of $word is $translation\n";
}
}
}

Another simple Perl question...(hash to an array of objects)

I'm working on creating a Graph class in Perl and I'm hoping to have each Graph contain Nodes. Each Node has some properties and also (for now) an array which contains references to each other node it is connected to. So far I have this definition of a node:
use strict;
package Node;
sub new{
my $class = shift;
my #array = ();
my $array_r = \#array;
my $self = {
code => undef,
name => undef,
country => undef,
continent => undef,
timezone => undef,
coordinates => undef
population => undef,
region => undef,
arrayRef => $array_r,
#_,
};
bless $self, $class;
return $self;
}
Yet upon calling the following function from my main script:
sub getSetArray{
my $self = shift;
my $param = shift;
my $temp = $self->{arrayRef};
push(#{$temp}, $param) if defined $param;
return $self->{arrayRef};
}
and trying to iterate over a Node's array(which will contain more Nodes that it is connected to):
my $firstnode = Node->new(); # Node constructor should have #array of
my $secondnode = Node->new();
$firstnode->getSetCode("test");
print "The current code is ", $firstnode->getSetCode(), "\n";
my $array_r = $firstnode->getSetArray($secondnode);
$array_r = $firstnode->getSetArray($firstnode);
foreach my $obj (#{$array_r}){
print $obj;
}
This prints out Node=HASH(0x10092bb00)Node=HASH(0x100907bd8). Which leads me to believe that I am dealing with an array containing 2 nodes (this is what I want). But upon attempting to call any methods of this $obj's I am told that I
Can't call method "getSetNode" without a package or object reference.
I had already previously blessed these two objects when calling new on these nodes. So I'm not sure why they aren't recognized as Nodes and I can't call their methods....
EDIT -
foreach my $obj (#{$array_r}){
print $obj->getSetCode();
}
where getSetCode() is
sub getSetCode{
my $self = shift;
my $param = shift;
$self->{code} = $param if defined $param;
return $self->{code};
}
Show how you are trying to call getSetNode on $obj, and the code of getSetNode?
By the way, $temp isn't needed; you can directly do:
push #{ $self->{arrayRef} }, $param if defined $param;
Maybe helpful to you: http://perlmonks.org/?node=References+quick+reference
Also note that you are setting up a cyclical data structure ($firstnode indirectly referencing itself) that perl won't garbage collect automatically, even when all external references go away; you can fix this (if this is even a concern) with:
if (defined $param) {
push #{ $self->{arrayRef} }, $param;
Scalar::Util::weaken( $self->{arrayRef}[-1] ) if $param == $self;
}
As far as your problem goes, putting all your code together (and adding a missing , on the coordinates line) into this:
use warnings;
use strict;
package Node;
sub new {
my $class = shift;
my #array = ();
my $array_r = \#array;
my $self = {
code => undef,
name => undef,
country => undef,
continent => undef,
timezone => undef,
coordinates => undef,
population => undef,
region => undef,
arrayRef => $array_r,
#_,
};
bless $self, $class;
return $self;
}
sub getSetArray{
my $self = shift;
my $param = shift;
my $temp = $self->{arrayRef};
push(#{$temp}, $param) if defined $param;
return $self->{arrayRef};
}
sub getSetCode{
my $self = shift;
my $param = shift;
$self->{code} = $param if defined $param;
return $self->{code};
}
my $firstnode = Node->new(); # Node constructor should have #array of
my $secondnode = Node->new();
$secondnode->getSetCode("test2");
$firstnode->getSetCode("test");
print "The current code is ", $firstnode->getSetCode(), "\n";
my $array_r = $firstnode->getSetArray($secondnode);
$array_r = $firstnode->getSetArray($firstnode);
foreach my $obj (#{$array_r}){
print $obj->getSetCode(), "\n";
}
gives this output for me:
The current code is test
test2
test

Resources