Perl: Use of reference "HASH(0x13bd718)" as array index at - arrays

I'm writing a script to implement game 2048 to practice perl, and an error pops up occationally as something like this:
Use of reference "HASH(0x13bd718)" as array index at 2048.pl line 181, line 11.
The code involved is for a subroutine like this:
sub gen1{
my #free_locs = {};
my $length;
my $rand_loc;
my $insert_loc;
for ($i=0; $i<16; $i++){
if($all_lines[$i] == 0){push #free_locs, $i;}
}
$length = #free_locs;
$rand_loc = int rand $length;
if ($rand_loc == $length) {$rand_loc--;}
$insert_loc = $free_locs[$rand_loc];
$all_lines[$insert_loc] = &generate();
&row_update;
&col_update;
}
At first I was writing that like as
$all_lines[$free_locs[$rand_loc]] = &generate();
The error seems to pop up more often. Then I switched to the code in the subroutine shown above, which seems to reduce the chance for it to happen, but it still happens...
Is there anything wrong with my way of coding here? What's the cleanest way of writing such piece of code?
Thanks and regards,
Terry

my #free_locs = {}; declares #free_locs to be a one-element array whose first element is a reference to an empty hash ({}). Thus, whenever $rand_loc is 0, $insert_loc will be a hash, and trying to use that hash as an index in $all_lines[$insert_loc] produces the error. To declare #free_locs as an empty array, write either my #free_locs = (); or just my #free_locs;.

Related

Passing array arguments to Perl subroutine

I am new to perl programming and I am trying to build a script using several subroutines on it. For a start I am trying to run a short mocke script to work out subroutines behaviour, but I don't get to understand the input.
Here is my code:
sub prueba{
my (#array1, #array2)=#_;
if (scalar(#array1)<scalar(#array2)) {
print #array1,"\n";
} elsif (scalar(#array1)>scalar(#array2)){
print #array2,"\n";
}
};
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(#primero,#segundo);
I am passing two arrays and I want the subroutine to retrieve the answer according to those arrays, but when I run it I get no output, not even warning errors messages... I already tried using the refference to the array, but still not working:
sub prueba{
my (#array1, #array2)=#_;
if (scalar(#array1)<scalar(#array2)) {
print #array1,"\n";
} elsif (scalar(#array1)>scalar(#array2)){
print #array2,"\n";
}
};
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(\#primero,\#segundo);
You can't pass arrays to subs (and they can't return them). You can only pass a number of scalars. What you are doing is equivalent to the following:
prueba(1,5,9,1,7,8,9,6,5,6,9);
All of the arguments end up in #array1. What we do is pass references to arrays.
prueba(\#primero,\#segundo);
But that also requires changing the sub. Without change, all of the arguments still end up in #array1. See perlreftut for a start on working with references. You can use
sub prueba{
my ($array1, $array2)=#_;
if (scalar(#$array1)<scalar(#$array2)) {
print "#$array1\n";
} elsif (scalar(#$array1)>scalar(#$array2)){
print "#$array2\n";
}
}
or just
sub prueba {
my ($array1, $array2) = #_;
if (#$array1 < #$array2) { say "#$array1"; }
elsif (#$array1 > #$array2) { say "#$array2"; }
}
< and > expect a number, so they already impose scalar context. And might as well use say, though that requires use feature qw( say ); (or something like use 5.014; which does the trick as well).
You can use prototypes to make it look like you're passing multiple arrays, and have perl turn them automatically into references:
sub prueba :prototype(\#\#) {
my ($array1, $array2) = #_;
if (#$array1 < #$array2) {
print #$array1,"\n";
} elsif (#$array1 > #$array2){
print #$array2,"\n";
}
}
my #primero=(1,5,9);
my #segundo=(1,7,8,9,6,5,6,9);
prueba(#primero, #segundo);
But read the documentation carefully to understand the cases where the subroutine can be called without enforcing the prototype.
Thanks all I just figured out what I wanted. I found that I can actually pass an array to perl, however maybe I am not explaning mysfel properly.The thing is to load the arrays as follow, inside the subroutine.
my #primero=#{$_[0]};
my #segundo=#{$_[1]};
This means we are using the reference. Ehen running the function, we must write the \ before each input:
prueba(\#primero,\#segundo);

Perl: array goes empty after passing it to a function?

I'm working on a project that's scalating a lot, lately, and I'm re-writing code to make it more OOP and passing all redundant code into sub-routines.
The script checks whether a gene exists in the database (through various means) or not. It may also report possible duplicates. Before reporting a duplicate, the script makes sure it's not a "biological duplicate" (essentially the same biological data but a with different position in the genome and, hence, not an actual duplicate). In order to do so...
my #gene_ids;
my #gene_names;
while(my $gene = $geners_bychecksum->next){
my $gene_name = $gene->gene_name;
my $gene_id = $gene->gene_id;
push #gene_ids, $gene_id;
push #gene_names, $gene_name;
}
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
my $solve_multihit = solve_multihit($id, \#gene_names, \#gene_ids, $spc, $species_directory, $dataset);
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
if($solve_multihit){
print STDERR "$id\tM\tUPDATE \n";
print $report "$id\tM\tUPDATE \n";
$countM++;
} else {
print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",#gene_names).")\n";
}
Here, $geners_bychecksum is a DBIC resulset with database hits from a prior search and, for this case-scenario, it always has more than 1 gene. The $id,$spc,$species_directory and $dataset are all strings that come from the config and are defined above this chunk.
The solve_multihit subroutine is a rather complicated function that tries to resolve whether the multi-hits are actual duplicates or biological duplicates. Notice that I'm passing the #gene_names and #gene_ids arrays to this function. This function will return the gene_id of the proper gene, if it was able to solve the discrepancy; or 0 if not. Simplified code for the sub can be found in the following link
https://codeshare.io/2EM8qN
THE ACTUAL QUESTION
You may have noticed that the
print STDERR "$id\tJ\tALERT CHECKSUM MULTI HIT\t(".join(",",#gene_names).")\n";
is both before and after the solve_multihit subroutine call... and the array seems to go empty after running the function, according to the STDERR:
BBOV_I005030 J ALERT CHECKSUM MULTI-HIT (XP_001609152.1,XP_001609157.1)
BBOV_I005030 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005040 J ALERT CHECKSUM MULTI-HIT (XP_001609156.1,XP_001609153.1)
BBOV_I005040 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005050 J ALERT CHECKSUM MULTI-HIT (XP_001609154.1,XP_001609155.1)
BBOV_I005050 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005060 J ALERT CHECKSUM MULTI-HIT (XP_001609154.1,XP_001609155.1)
BBOV_I005060 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005070 J ALERT CHECKSUM MULTI-HIT (XP_001609156.1,XP_001609153.1)
BBOV_I005070 J ALERT CHECKSUM MULTI-HIT ()
BBOV_I005080 J ALERT CHECKSUM MULTI-HIT (XP_001609152.1,XP_001609157.1)
BBOV_I005080 J ALERT CHECKSUM MULTI-HIT ()
Why would that happen? I'm pretty sure I could solve it by returning the arrays along with the results of the solve_multihit{} sub, but I wonder why would it go empty.
PS: The J in the report is just a case-scenario key code.
my #gene_names = splice(shift);
my #gene_ids = splice(shift);
is short for
my #gene_names = splice(#{ shift(#_) });
my #gene_ids = splice(#{ shift(#_) });
splice(#a) empties the array and returns its contents. There's no reason to do that! The above should be
my #gene_names = #{ shift(#_) };
my #gene_ids = #{ shift(#_) };
Honestly, there's no need to make a copy of the array. Just use the provided reference.
my $gene_names = shift;
my $gene_ids = shift;
I'd provide a fixed-up version of solve_multihit, but it has numerous major problems I can't fix with the information I have.
I can see two ways for your code to accomplish the data removal that it seems to be doing.
The function arguments available in #_ are aliased to data passed to it. So if you change #_ itself (or its elements) you change the data outside of the function.
More likely, as you are passing by reference, your sub probably works directly with it
sub ff {
my ($rary) = #_;
#$rary = ();
}
my #data = 1..4;
ff(\#data);
say for #data; # empty
If your processing needs to change the array it works with then make a local copy first
sub ff {
my ($rary) = #_;
my #local_ary = #$ary;
# now changes to #local_ary do not affect #data in the caller
}
This is generally safer, while it does introduce a data copy which doesn't happen when working with the reference.
The edit together with ikegami's answer clears this up: splice is destructive to the array it works with and here by curious syntax it's fed an anonymous array formed out of a dereferenced #_ argument, whereby it changes the data in the caller.
There is no reason for splice in what you do. Its purpose is to change the array.
Instead, use arrayrefs that are passed to the sub
sub solve_multihit {
my ($id, $gene_names, $gene_ids, ...) = #_;
foreach my $name (#$gene_names) {
...
}
...
}
or make a local copy if you wish
sub solve_multihit {
my $id = shift;
my #gene_names = #{ shift #_ };
...
}
where my #gene_names is a lexical variable in this scope (the sub in your case ) and changes to it do not affect the one with the same name in the calling scope.

How to use IDL's min() function's min_subscript argument in for loop?

I have experience in python but am new to IDL. I am trying to write a function that will return two bins. I want to use the min function to get my bin edges. My issue is that I am trying to use the min_subscript argument to denote each bin edge, and I can't figure out how to do this in a for loop. I want to write my code so that each loop has 2 different min_subscript variables (the two edges of the bin), and these variables are written into their own arrays. Here is my code:
FUNCTION DBIN, radius, data, wbin, radbin, databin
FOR i = 0, N_ELEMENTS(radius)-1 DO BEGIN
l = lonarr(N_ELEMENTS(radius))
m = lonarr(N_ELEMENTS(radius))
junk1 = min(abs(radius - radius[i]), l[i])
junk2 = min(abs(radius - (radius[i] + wbin)), m[i])
radbin = lonarr(N_ELEMENTS(radius))
radbin[i] = radius[l[i]:m[i]]
databin = lonarr(N_ELEMENTS(data))
databin[i] = total(data[l[i]:m[i]])
ENDFOR
END
wbin is the desired bin width. The junk variables only exist for the purpose of getting the min_subscripts at those locations. The min_subscripts are the l[i]'s and the m[i]'s.
I appreciate any help!!
The min_subscript argument is trying to pass a value back to you, so you must pass a "named variable" to it. Named variables have pass by reference behavior. So you have to do it in two steps, something like:
junk1 = min(abs(radius - radius[i]), li)
l[i] = li
Above, li is a named variable, so it can receive and pass back the value. Then you can put it in your storage array.

Error while using removeChild() and accessing members of array

I am stuck doing this even though I know it's very simple. Yet, I am getting errors.
What I have:
I have 3 arrays.
1st Array contains objects of UpgradeButton class.
2nd Array contains objects of BuyButtonclass.
3rd Array named newCostlyShops contains Numbers.
BuyButton class and UpgradeButton class, both have a shopCode member which is a number; the number which I'm trying to equate.
What I'm trying to do:
My goal is to first look for BuyButton and UpgradeButton objects in the respective arrays which have shopCodes same as those in newCostlyShops.
After that, I removeChild() that object and splice it out from the array.
My Code:
Array 3:
var newCostlyShops:Array = new Array();
newCostlyShops = Object(root).WorkScreen_mc.returnCostlyShops();
trace(newCostlyShops); // this is tracing out the exact shopCodes I want and is working fine.
Deletion and Splicing codes:
for (looper = 0; looper < upgradeButtonsArray.length; looper++) {
for (var secondLooper: int = 0; secondLooper < newCostlyShops.length; secondLooper++) {
if (upgradeButtonsArray[looper].shopCode == newCostlyShops[secondLooper]) {
trace(looper);
trace(upgradeButtonsArray[looper]);
removeChild(upgradeButtonsArray[looper]);
upgradeButtonsArray.splice(looper, 1);
}
}
}
for (looper = 0; looper < buyButtonsArray.length; looper++) {
for (secondLooper = 0; secondLooper < newCostlyShops.length; secondLooper++) {
if (buyButtonsArray[looper].shopCode == newCostlyShops[secondLooper]) {
trace(looper);
trace(buyButtonsArray[looper]);
removeChild(buyButtonsArray[looper]);
buyButtonsArray.splice(looper, 1);
}
}
}
What's wrong with this Code:
I keep getting error
TypeError: Error #1010: A term is undefined and has no properties.
This error comes only after the 1st time this code is run and not the first time it is run. When I remove the removeChild and splice , this traces out objects that are not null, ever. Even after this whole function is called 100 times, the error is not shown. Only when I removeChild and use splice this occurs.
Is there something wrong with what I'm doing? How to avoid this error? This is throwing the whole program haywire. If there is any other alternative to this method, I'm open to take those methods as well as long as I don't get errors and my goal is reached.
It might sounds funny, but.... try to decrement looper after splicing.
trace(looper);
trace(upgradeButtonsArray[looper]);
removeChild(upgradeButtonsArray[looper]);
upgradeButtonsArray.splice(looper, 1);
looper--;
I think after splicing the array all item's are being shifted and you're skipping next one.
Also, you should get some more information with this error, like which class/line is throwing it. Maybe you need to enable "permit debugging" or something?
Bonus suggestion:
For newCostlyShops use Dictionary instead of Array so you won't have to nest for inside for...

Perl Modification of non creatable array value attempted, subscript -1

I have a Perl-Script, which executes a recursive function. Within it compares two elements of a 2dimensional Array:
I call the routine with a 2D-Array "#data" and "0" as a starting value. First I load the parameters into a separate 2D-Array "#test"
Then I want to see, if the array contains only one Element --> Compare if the last Element == the first. And this is where the Error occurs: Modification of non creatable array value attempted, subscript -1.
You tried to make an array value spring into existence, and the subscript was probably negative, even counting from end of the array backwards.
This didn't help me much...I'm pretty sure it has to do with the if-clause "$counter-1". But I don't know what, hope you guys can help me!
routine(#data,0);
sub routine {
my #test #(2d-Array)
my $counter = $_[-1]
for(my $c=0; $_[$c] ne $_[-1]; $c++){
for (my $j=0; $j<13;$j++){ #Each element has 13 other elements
$test[$c][$j] = $_[$c][$j];
}
}
if ($test[$counter-1][1] eq $test[-1][1]{
$puffertime = $test[$counter][4];
}
else{
for (my $l=0; $l<=$counter;$l++){
$puffertime+= $test[$l][4]
}
}
}
#
#
#
if ($puffertime <90){
if($test[$counter][8]==0){
$counter++;
routine(#test,$counter);
}
else{ return (print"false");}
}
else{return (print "true");}
Weird thing is that I tried it out this morning, and it worked. After a short time of running he again came up with this error message. Might be that I didn't catch up a error constellation, which could happen by the dynamic database-entries.
Your routine() function would be easier to read if it starts off like this:
sub routine {
my #data = #_;
my $counter = pop(#data);
my #test;
for(my $c=0; $c <= $#data; $c++){
for (my $j=0; $j<13;$j++){ #Each element has 13 other elements
$test[$c][$j] = $data[$c][$j];
}
}
You can check to see if #data only has one element by doing scalar(#data) == 1 or $#data == 0. From your code snippet, I do not see why you need to copy the data to passed to routine() to #test. Seems superfluous. You can just as well skip all this copying if you are not going to modify any of the data passed to your routine.
Your next code might look like this:
if ($#test == 0) {
$puffertime = $test[0][4];
} else {
for (my $l=0; $l <= $counter; $l++) {
$puffertime += $test[$l][4];
}
}
But if your global variable $puffertime was initialized to zero then you can replace this code with:
for (my $l=0; $l <= $counter; $l++) {
$puffertime += $test[$l][4];
}

Resources