Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring sets of anonymous arrays in perl

Tags:

perl

I'm trying to generate arrays for each of the agregations of cells in suduko. I seem to have fixed the problem, but don't understand what my earlier alternatives are doing.

I get the answer I expected if I write for instance:

@row = ( [], [], [], [], [], [], [], [], [] ) ;

I had expected

@row = ( [] ) x 9 ;

to behave the same. I also tried, which did better

@row = ( [] x 9 ) ;

Only the first element comes out strange with this, in two of the arrays. With the first rejected form I get all 81 elements in each array

I did wonder if the last form was actually legal?

# prob.pl - Show problem with repeat anonymous arrays
#
# ###################################################

@row = ( [] x 9 ) ;
@col = ( [] x 9 ) ;
@box = ( [] x 9 ) ;

for ( $i = 0 ; $i < 81 ; $i ++ ) {
   push( @{$row[ row($i) ]}, $i ) ;
   push( @{$col[ col($i) ]}, $i ) ;
   push( @{$box[ box($i) ]}, $i ) ;
}

for ( $i = 0 ; $i < 9 ; $i ++ ) {
   print STDERR "\@{\$row[$i]} = @{$row[$i]}\n" ;
   print STDERR "\@{\$col[$i]} = @{$col[$i]}\n" ;
   print STDERR "\@{\$box[$i]} = @{$box[$i]}\n" ;
}

sub row {
   my( $i ) = @_ ;

   int( $i / 9 ) ;
}

sub col {
   my( $i ) = @_ ;

   $i % 9 ;
}

sub box {
   my( $i ) = @_ ;

   int( col( $i ) / 3 ) + 3 * int( row( $i ) / 3 ) ;
}
like image 362
ChrisW Avatar asked Oct 29 '19 20:10

ChrisW


People also ask

How do I create an anonymous array in Perl?

You create an anonymous array by using square brackets like this: $arrayref = [ 'one', 'two', 'three' ]; There exists an array that has no name but is available via the $arrayref reference, which is just like any other array reference in all other respects.

How do I declare an array in Perl?

Array Creation: In Perl programming every array variable is declared using “@” sign before the variable's name. A single array can also store elements of multiple datatypes.

What is anonymous hash in Perl?

An anonymous hash is simply a hash without a name. Both named and anonymous hashes have references, and \%hash is no more a direct reference than { foo => "bar" } . You imply that the latter is an indirect reference.

What is the meaning of anonymous arrays explain with an example?

An array in Java without any name is known as an anonymous array. It is an array just for creating and using instantly. Using an anonymous array, we can pass an array with user values without the referenced variable. Properties of Anonymous Arrays: We can create an array without a name.


2 Answers

Answer in two parts: first part is a simple view of what is happening and what you should do to fix it. Second part tries to explain this weird behavior you're getting.


Part 1 - simple explanations

All forms are legal; they are just not equivalent and probably don't do what you expect. In such case, Data::Dumper or Data::Printer are your friends:

use Data::Printer;
my @a1 = ( [] x 9 );
p @1;

Prints something like

[
    [0] "ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)ARRAY(0x1151f30)"
]

Quoting the doc of x (the "repetition operator", if you need to search for it):

In scalar context, or if the left operand is neither enclosed in parentheses nor a qw// list, it performs a string repetition.

([] x 9 calls x in scalar context)

On the other hand, when you do ([]) x 9, you get something like:

[
    [0] [],
    [1] var[0],
    [2] var[0],
    [3] var[0],
    [4] var[0],
    [5] var[0],
    [6] var[0],
    [7] var[0],
    [8] var[0]
]

Quoting the doc again:

If the x is in list context, and the left operand is either enclosed in parentheses or a qw// list, it performs a list repetition. In that case it supplies list context to the left operand, and returns a list consisting of the left operand list repeated the number of times specified by the right operand.

What happens here, is that [] is evaluated before x is applied. It creates an arrayref, and x 9 then duplicates it 9 times.

Correct ways to achieve what you want would be either your first solution, or maybe, if you prefer something more concise (but still readable):

my @row = map { [] } 1 .. 9;

(since the body of the map is evaluated at each iteration, it creates indeed 9 distinct references)

Or, you could just not initialize @row, @col and @box and let autovivification creates the arrays when needed.


Part 2 - advanced explanation

You have some weird behavior with your program when you use ([] x 9). For simplicity, let me reproduce it with a simpler example:

use feature 'say';

@x = ([] x 5);
@y = ([] x 5);
@z = ([] x 5);

push @{$x[0]}, 1;
push @{$y[0]}, 1;
push @{$z[0]}, 1;

say "@{$x[0]}";
say "@{$y[0]}";
say "@{$z[0]}";

This program outputs:

1 1
1
1 1

Interestingly, removing the definition of @y (@y = ([] x 5)) from this programs produces the output:

1
1
1

Something fishy is going on. I'll explain it with two points.

First, let's consider the following example:

use Data::Printer;
use feature 'say';

say "Before:";
@x = "abcd";
p @x;
say "@{$x[0]}";

say "After:";
push @{$x[0]}, 5;
p @x;
say "@{$x[0]}";
say $abcd[0];

Which outputs

Before:
[
    [0] "abcd"
]

After:
[
    [0] "abcd"
]
5
5

When we do push @{$x[0]}, 5, @{$x[0]} becomes @{"abcd"}, which creates the arrays @abcd, and pushes 5 into it. $x[0] is still a string (abcd), but this string is also the name of an array.

Second*, let's look at the following program:

use Data::Printer;

@x = ([] x 5);
@y = ([] x 5);
@z = ([] x 5);

p @x;
p @y;
p @z;

We get the output:

[
    [0] "ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)"
]
[
    [0] "ARRAY(0x19b0188)ARRAY(0x19b0188)ARRAY(0x19b0188)ARRAY(0x19b0188)ARRAY(0x19b0188)"
]
[
    [0] "ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)ARRAY(0x19aff30)"
]

@x and @z contain the same reference. While this is surprising, I think that this is explainable: line 1, [] x 5 creates an arrayref, then converts it into a string to do x 5, and then it doesn't use the arrayref anymore. This means that the garbage collector is free to reclaim its memory, and Perl is free to reallocate something else at this address. For some reason, this doesn't happen right away (@y doesn't contain the same thing as @x), but only when allocating @z. This is probably just the result of the implementation of the garbage collector / optimizer, and I suspect it might change from a version to another.

In the end, what happens is this: @x and @z contains a single element, a string, which is identical. When you dereference $x[0] and $z[0], you therefore get the same array. Therefore, pushing into either $x[0] or $z[0] pushes into the same array.

This would have been caught with use strict, which would have said something like:

Can't use string ("ARRAY(0x2339f30)ARRAY(0x2339f30)"...) as an ARRAY ref while "strict refs" in use at repl1.pl line 11.

* note that for this second part, I am not sure that this is what happens, and this is only my (somewhat educated) guess. Please, don't take my word for it, and feel free to correct me if you know better.

like image 80
Dada Avatar answered Oct 13 '22 18:10

Dada


The second form creates 9 copies of the same array reference. The third form creates a single array element consisting of a string like "ARRAY(0x221f488)" concatenated together 9 times. Either create 9 individual arrays with e.g. push @row, [] for 1..9; or rely on autovivification.

like image 33
Dave Mitchell Avatar answered Oct 13 '22 19:10

Dave Mitchell