Access an object within an object? (Perl) - perl

I'm pretty new to perl. I have an SomeItem object that contains an array of InnerObjects, of which I want to call the "foo" method on.
foreach $obj (# { $self->{InnerObjects} }) {
$obj->foo();
}
This doesn't work. Here's the error I get:
Can't call method "foo" without a package or object reference
The InnerObject class is in the same file as SomeItem, and would prefer to keep it that way if possible, so how can I access the InnerObject class/package from the SomeItem class/package?
Here's how I declare the array in the constructor:
$self->{InnerObjects} = [];
and set it:
sub set {
my ($self, #items) = #_;
#{ $self->{InnerObjects} } = #items;
}

Your code so far looks legitimate. Therefore the error MAY be in the data passed to set().
Please add the following to set code:
sub set {
my ($self, #items) = #_;
#{ $self->{InnerObjects} } = #items;
print "OBJECT To work on: " . ref($self) . "\n";
print "TOTAL objects passed: " . scalar(#items) . "\n";
foreach my $obj (#items) { print "REF: " . ref($obj) . "\n" };
}
This will show how many objects you passed and whether they are indeed objects of correct class (ref should print class name)
Also, please be aware that #{ $self->{InnerObjects} } = #items; copies over the array of object references, instead of storing the reference to the original array #items - this is NOT the reason for your problem at all but causes you to basically allocate 2 arrays instead of one. Not a major problem memory-management wise unless the array is very large, but still wasteful (#items array would need to be garbage collected after set() is done).
I apologize for putting what was essentially comment-content as an answer but it's too big to be a comment.

My solution to the problem ended up creating a hash that contained the ID of the SomeItem which points to a reference of an array of InnerObjects. This hash is created by the manipulating classlike so,
%SomeItemInnerObjects; # Hash of SomeItem IDs=>InnerObject array references
$SomeItemInnerObjects{ $currentSomeItem->{ID} } = \#myInnerObjects;
and gets used as follows:
foreach $item (#{ $SomeItemInnerObjects{$currentSomeItem->{ID} } }) {
# item is an InnerObject
$item->foo($currentSomeItem->{ID});
}
So SomeItem no longer contains InnerObjects. I know this doesn't answer the question per se, but presents an alternate solution.

Related

Perl return list of array refs of unknown length

I have a sub in Perl that needs to return a list of array refs to fit in with the rest of the package. The problem is that I don't know in advance how many array refs I will generate. My usual method of pushing the array refs that I generate into an array and returning a reference to that doesn't work with the rest of the code, which I can't change without breaking some legacy stuff.
sub subTrackTable {
my ($self, $experimentName, $subTrackAttr) = #_;
# return nothing if no subtracks required
if ($subTrackAttr eq 'no_sub') {
return;
}
# get distinct values for subtrack attr (eg antibody) from db
my $dbh = $self->dbh();
my $sh = $dbh->prepare("SELECT DISTINCT * blah sql");
$sh->execute();
my #subtrackTable;
while (my ($term, $value) = $sh->fetchrow_array()) {
my $subtrack = [':$value', $value];
push (#subtrackTable, $subtrack);
}
$sh->finish();
# this is hard-coded for one experiment and does what I want
# Want to loop through #subtrackTable and return a list of all the array refs it contains
# Returning nested array refs doesn't work with external code
return ([":H3K4me3", "H3K4me3"],[":H4K20me3", "H4K20me3"]);
}
The problem is that because I am dynamically getting values from a database, I don't know how many there will be. Just returning \#subtrackTable, which would be my usual strategy breaks the rest of the code. If I knew in advance how many there would be I could also do something like
my $a1 = [":$value1", $value1];
my $a2 = [":$value2", $value2];
...
my $an = [":$valuen", $valuen];
return($a1, $a2,...$an);
but I can't see how to make this work with an unknown number of arrayrefs.
Help appreciated!
It looks like you just need to
return #subtrackTable;
Also, this line
my $subtrack = [':$value', $value];
must be changed to use double quotes, like this
my $subtrack = [ ":$value", $value ];

Irssi Loop Through Users in Channel

I am trying to piece together other scripts i have seen to be able to loop through a list of users on the channel.
Here is what i have come up with
my $channel = #_;
foreach my $nick ($channel->nicks()) {
$server->command("msg $chatchannel $nick->{nick}");
}
But all i get from that is
Can't call method "nicks" without a package or object reference at
/root/.irssi/scripts/test.pl line 64.
which is referring to
$channel->nicks()
Am i going about this the wrong way? or should this be working? I have seen many other scripts using $channel->nicks() so i know it must work?
Edit
I should also mention that this is already define further up in the code
my ($server, $msg, $target, $channel, $chatnet) = #_;
But when i try it with that $channel variable i get
Can't locate object method "nicks" via package
"mattigins#mattigins.tmi.twitch.tv" (perhaps you forgot to load
"mattigins#mattigins.tmi.twitch.tv"?) at /root/.irssi/scripts/test.pl
line 64.
Since the left hand side (LHS) of my $channel = #_; is a scalar it imposes scalar context on the #_ array. This means that the length of the array gets assigned to $channel. You want to assign with my ($channel) = #_; so that the LHS is in list context and that the first element in the #_ array gets assigned to your scalar.
Ref:
What is the difference between the scalar and list contexts in Perl?
Scalar and List context in Perl
I figured out how to do it.
$chan = $server->channel_find('#channel');
foreach my $nick ($chan->nicks()) {
$nickname = $nick->{nick};
}

how to copy(insert) hash reference to another hash reference in perl?

recently started doing perl. I'm reading old code and trying to rewrite some stuff. I've got a question here about hash references.
#declar anon hash ref that will be returned
my $store_hash = {};
foreach my $item (#list)
{
#this will iterate based on list
my $ret_hash = ops_getval($item, $user)
#do some magic here to map $ret_hash into $store_hash
}
ops_getval is a function that returns a type of ref hash. I want to insert those values into $store_hash. How should I approach this? Can I directly do
$store_hash = ops_getval($var1,$var2)
Much appreciated!
I think the standard way to do this is:
#$store_hash{ keys %$ret_hash } = values %$ret_hash;
This merges all of the hashes returned by all of the calls to ops_getval into $store_hash.
An alternate approach that might be clearer to the eye, possibly at the cost of a lot of redundant data copying:
%$store_hash = (%$store_hash, %$ret_hash);
You would do something like:
$store_hash->{$item} = $ret_hash
In general:
$hashref->{$key} = $value
See here for more: http://perldoc.perl.org/perlref.html#Using-References
To be clear, you can use a loop and get this done.
foreach ( keys%{ $ret_hash } ){
$store_hash->{ $_ } = $ret_hash->{ $_ } ;
}

How to store a reference in perl that is itself not a reference

I have a recursive function that uses ref to walk down a data structure recursively. If ref returns an empty string, then a callback is called. My problem is that I need to store a hash-ref in my data structure that will be treated as a scalar by my function.
In essence, what I need is to do something like this;
my %hash = fillHash();
$hash{'abc'}{'def'} = \%hash;
I was wondering if there was some way of storing \%hash in such a way that it would be treated as a scalar and not a reference. possibly like/
$hash{'abc'}{'def'} = scalar \%hash;
I am simply looking a way to add a link to the parent node of my data structure, yet still being able to recursively walk it.
Thanks
You could use what I did to differentiate values from structure, I call them "indirect arrays" (in my case).
In your case it would look like this:
$hash{'abc'}{'def'} = scalar \\%hash;
ref( $hash{'abc'}{'def'} ) is 'REF', in case you're wondering. And you can then decide that you need to dereference it twice:
$hash_ref = ${ $hash{'abc'}{'def'} };
I think #MarkCanlas has the right solution in his suggestion to consider a different way to structure your data. Barring that, you could take a double reference.
my $scalar_ref = \%hash;
$other_hash{abc}{def} = \$scalar_ref;
Now, when you check the ref of that you'll get back "REF" and can do something different.
First of all, you should realize that all references are scalars by definition.
What you want is something that works as a reference everywhere but inside your walking function. That's not really achievable by any kind of magic reference. The walking function is where the logic is going to have to be added. You can make it keep track of things it has already seen:
my %h = (
foo => "bar",
inner => {
foo => 42
}
);
$h{inner}{parent} = \%h;
sub walk
{
my ($h, $cb, $path, $seen) = #_;
$path //= "";
$seen //= {};
$seen->{$h}=1;
while(my ($k, $v) = each %$h) {
if(ref($v)) {
walk($v, $cb, "$path/$k", $seen) if !$seen->{$v};
} else {
$cb->($k, $v, "$path/$k");
}
}
}
walk(\%h, sub {
my ($key, $value, $path) = #_;
print "walker found $key ($path) => $value\n";
});
Or you could just make it recognize certain keys like parent as special and skip them.

Perl subroutine array and scalar variable parameters

How exactly can I pass both scalar variables and array variables to a subroutine in Perl?
my $currVal = 1;
my $currValTwo = 1;
my #currArray = ('one','two','three');
my #currArrayTwo =('one','two','three');
&mysub($currVal, $currValTwo,\#currArray, \#currArrayTwo);
sub mysub() {
# That doesn't work for the array as I only get the first element of the array
my($inVal, $inValTwo, #inArray, #inArrayTwo) = #_;
}
You need to fetch them as references because you've already passed them as references (by using the \ operator):
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
and then use the references as arrays:
#{$inArray}
You pass the arguments as references, so you need to dereference them to use the values. Be careful about whether you want to change the original array or not.
sub mysub {
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
#{$inArrayTwo} = ('five','six','seven');
}
This will change the original #currArrayTwo, which might not be what you want.
sub mysub {
my($inVal, $inValTwo, $inArray, $inArrayTwo) = #_;
my #ATwo = #{$inArrayTwo};
#ATwo = ('five','six','seven');
}
This will only copy the values and leave the original array intact.
Also, you do not need the ampersand in front of the sub name, from perldoc perlsub:
If a subroutine is called using the &
form, the argument list is optional,
and if omitted, no #_ array is set up
for the subroutine: the #_ array at
the time of the call is visible to
subroutine instead. This is an
efficiency mechanism that new users
may wish to avoid.
You do not need empty parens after your sub declaration. Those are used to set up prototypes, which is something you do not need to do, unless you really want to.
So, for example: This is a using statement to search something in an array:
use List::Util qw(first);
This is the sub declaration:
sub GetIndex($$$);
This is the call to the sub (last parameter is: Default index value to give back if not found)
$searchedIndex = GetIndex(\#theArr, "valuesearched", 1);
This is the routine:
sub GetIndex($$$)
{
my $inArray=shift;
my #theArray= #{$inArray};
my $searchedTag= shift;
my $defaultVal= shift;
my $retVal = first { $theArray[$_] eq $searchedTag} 0 .. $#theArray;
if ((! defined $retVal)|| ($retVal<0)||($retVal>#theArray))
{
$retVal = $defaultVal;
}
return $retVal;
}