Perl - Assign an array to another variable - arrays

I'm trying to assign an array to a value in my hash as follows:
$authors->[$x]->{'books'} = #books;
$authors is an array of hashes that contains his/her first name, last name, date of birth, etc. And now I'm creating a books key where I want to assign an array of books. However, when I try to print it afterwords, it's just printing the size of the array, as if I'm doing $value = scalar #books.
What am I doing wrong?

Array elements and hash values are scalars, so when you are nesting arrays and hashes, you must use references. Just as $authors->[$x] is not a hash but a reference to a hash, you must set $authors->[$x]->{'books'} to a reference to the array.
$authors->[$x]->{'books'} = \#books; # reference the original array
$authors->[$x]->{'books'} = [#books]; # reference a copy
You would then access elements of the array using something like
$authors->[$x]->{'books'}->[0]
which can be abbreviated
$authors->[$x]{books}[0]
or access the whole array as
#{$authors->[$x]{books}}
Your original attempt
$authors->[$x]->{'books'} = #books;
is exactly equivalent to
$authors->[$x]->{'books'} = scalar #books;
because the left operand of the = operator is a hash value, which is a scalar, so the right operand is evaluated in scalar context to provide something that can be assigned there.
P.S.
On rereading this answer I realized it may be confusing to say "a hash value is a scalar" because of the possible interpretation of "hash value" as meaning "the value of a hash variable" i.e. "the whole hash". What I mean when I write "hash value" is an item that is stored in a hash as a value (as opposed to a key).

While the first awnser is absolutely right,
as an alternative, you could also fo this:
push #{$authors->[$x]->{'books'}}, #books;
Then $authors->[$x]->{'books'} will be an Array that contains a copy of all
the elements from #books.
This might be more "foolproof" then working with references, as
mentioned above.

Related

2D Array Printing as Reference

I have the code similar to below:
my #array1 = (); #2d array to be used
my $string1 = "blank1";
my $string2 = "blank2";
my $string3 = "blank3";
my #temp = ($string1, $string2, $string3);
push (#array1, \#temp);
The reason I am assigning the strings and then putting them into an array is because they are in a loop and the values get updated in the loop (#array1 is not declared in the loop).
When I run my program, it only gives me a reference to an array rather than an actual 2D array. How can I get it to print out the content as a 2D array and not as a reference or flattened out to a 1D array?
I would like an output like [[blank1, blank2, blank3],....] so i can access it like $array1[i][j]
An array can only have scalars for elements. Thus this includes references, to arrays for example, what enables us to build complex data structures. See perldsc, Tom's Perl Data Structure Cookbook.
Elements of those ("second-level") arrays are accessed by dereferencing, so $array1[0]->[1] is the second element of the array whose reference is the first element of the top-level array (#array1). Or, for convenience, a simpler syntax is allowed as well: $array1[0][1].
If we want a list of all elements of a second-level array then dereference it with #, like:
my #l2 = #{ $array1[0] }; # or, using
my #l2 = $array1[0]->#*; # postfix dereferencing
Or, to get just a few elements of the array, but in one scoop -- a slice
my #l2_slice = #{$array1[0]}[1..2]; # or
my #l2_slice = $array1[0]->#[1..2]; # postfix reference slice
what returns the list with the second and third elements of the same second-level array.
The second lines are of a newer syntax called postfix dereferencing, stable as of v5.24. It avails us with the same logic for getting elements as when we drill for a single one, by working left-to-right all the way. So ->#* to get a list of all elements for an arrayref,->%* for a hashref (etc). See for instance a perl.com article and an Effective Perler article.
There is a thing to warn about when it comes to multidimensional structures built with references. There are two distinct ways to create them: by using references to existing, named, variables
my #a1 = 5 .. 7;
my %h1 = ( a => 1, b => 2 );
my #tla1 = (\#a1, \%h1);
or by using anonymous ones, where arrayrefs are constructed by [] and hashrefs by {}
my #tla2 = ( [ 5..7 ], { a => 1, b => 2 } );
The difference is important to keep in mind. In the first case, the references to variables that the array carries can be used to change those variables -- if we change elements of #tla1 then we really change the referred variables
$tla1[0][1] = 100; # now #a1 == 5, 100, 7
Also, changing variables with references in #tla1 is seen via the top-level array as well.
With anonymous arrays and hashes in #tla this isn't the case as elements (references) of #tla access independent data, which cannot be accessed (and changed) in any other way.
Both of these ways to build complex data structures have their uses.

Perl: Negative range indexing of array reference

I have an array reference that I would like to slice the last two elements of the array. I found that using -2..-1 would work. I was using the following syntax:
subroutine($var->[-2..-1]);
This gave me the following error:
Use of uninitialized value $. in range (or flip)
Argument "" isn't numeric in array element
I changed the line to this and that worked:
subroutine(#$var[-2..-1]);
I don't understand why the second way works though and the first doesn't. I thought using the array operator was the same as dereferencing with #. Is the context ambiguous with the arrow operator?
-> is the dereference operator. $aref->[$i] is to an $aref like $arr[$i] is to #arr. To get a slice from an array, you need to change the sigil: #arr[$i, $j]. It's similar for dereference, but instead of changing the sigil, you first dereference the reference, then slice it:
#{ $aref }[$i, $j]
which can be shortened to #$aref[$i, $j].
So the -> operator can only be used for single values for array and hash references. You need #{} for slices.

printing out the values of a hash coming from an array

I have a hash called sales_hash that I am printing out. Each hash has a key called sku that matches a hash inside the array of array_items. I get the hash out of the array and am trying to print the values of the hash based on the key which is :item but I keep getting an error. What am I doing wrong?
sales_hash.take(10).each do |a, b|
temp_hash = array_items.select{|array_items| array_items[:sku] == a}
puts temp_hash
puts "Sku is #{a} the amount sold is #{b} the name of the book is #{temp_hash[:price]}"
end
The line #{temp_hash[:item]}" keeps giving me an error
Your temp_hash is actually an array.
From the Docs:
select -> Returns a new array containing all elements of ary for which the given block returns a true value.
And you can't access and array like this: array[:key].
Since temp_hash is an array, and you're sure that there's only one item inside that array, the proper way to get the content of temp_hash is using "first" like this:
#{temp_hash.first[:price]}
As your temp_hash is an array, so you can access the expected hash like this:
temp_hash[0] # this will give you the expected hash data
And, then you can access the required key inside the hash (e.g. price):
temp_hash[0][:price]

Why "array.length" returns array object when we put it inside array[]?

rand(array.length) # returns random index <br>
array[rand(array.length)] # returns random array object
I can't understand the logic behind. I would assume that second example also returns random index and then store it in array.
kitty = [100,102,104,105]
rand(kitty.length) # returns index, for example 3 ( or 0,1,2 )
array[rand(kitty.length)] # returns random array object, for example 104 ( or 100,102,105)
While array.sample would be the best way to get a random element from an array, I believe OP is asking how the chaining of methods works.
When you call: rand(array.length) a number is returned, true. However in the case of:
array[ rand(array.length) ]
a number is returned but then fed immediately into the array call, which gives you the object at that array index.
Is this a tutorial? If so, don trust it. array.sample is how to do it.
ruby code
kitty = [100,102,104,105]
kitty.sample #to get random array elements
The behavior you are describing is expected:
array[index] is a reference to an object in the provided array where index is a numeric value that is less than array.length since rand(array.length) returns a random numeric value less than array.length you are effectively accessing an element at a random index of the given array.
The same behavior can be obtained with array.sample though and is recommended.
For more information on Ruby's arrays please see the documentation at: http://ruby-doc.org/core-2.2.0/Array.html

Fun with array of arrays

I'm a total Perl newb, but still cannot believe I cannot figure this out with all the info I've read through online, but, I've burned too much time and am suffering from block at this point. Hoping to learn something based on my real life example...
Ok, I think I have an array of arrays, created like this:
my #array1 = ();
my #array2 = ();
my $ctr1 = 0;
my $col;
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$array1[$ctr1]=$col;
$ctr1++;
}
print STDERR "#array1";
##results in 10 rows, a mac address in each
##00:00:00:00:00:00 00:11:11:11:11:11 22:22:22:22:22:22 33:33:33:33:33:33 ...
Now I do another query. While looping through results, I am looking for those 10 mac addresses. When I find one, I add a row to array2 with the mac and the sequential number accumulated to the point, like this:
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$ctr2++;
if( my ($matched) = grep $_ eq $col, #array1 ) {
push( #array2, ($col,$ctr2) );
}
}
print STDERR "#array2";
##results in 10 rows, a mac address and an integer in each
##00:00:00:00:00:00 2 00:11:11:11:11:11 24 22:22:22:22:22:22 69 33:33:33:33:33:33 82 ...
Now the easy part. I want to loop through array2, grabbing the mac address to use as part of a sql query. Therein lies the problem. I am so ignorant as to exactly what I am doing that even though I had it almost working, I can't get back to that point. Ignorance is definitely not bliss.
When I loop through array2, I am getting a host of errors, based on the different forms of the statement. The one I think is right is listed below along with the error message...
my $ctr3 = 0;
foreach $ctr3 (#array2) {
my $chkmac = $array2[$ctr3][0]; <--- gacks here with the error below - line 607
[SQL query]
[Thu May 30 14:05:09 2013] [error] Can't use string ("00:66:55:77:99:88") as an ARRAY ref while "strict refs" in use at /path/to/test.cgi line 607.\n
I believe the issue is that my array of arrays is not an array of arrays. If it were, it would work as coded, or so I think from the reading... That said, I cannot fathom what I am dealing with otherwise. This will be a head slapper I'm all but sure, but I am stumped.... Little help, please?
TIA
O
For an array of arrays you want to use an array reference, e.g.
push #array2, [$col, $ctr2];
When accessing an element within an array refernce, you'll want to use the -> operator. Also, when looping through an array, it's not necessary to index back into that same array. So the last part would look more like:
foreach $ctr3 (#array2) {
my $chkmac = $ctr3->[0];
....
When you do the foreach there, $ctrl3 won't have the index in it, it'll have the value. So you should just need to do $ctrl3->[0]. Note the -> which dereferences the array reference (#array2 is actually an array of array references).
EDIT: As AKHolland pointed out, #array2 actually isn't an array of array references, although that's what it should be. You also need to change:
push( #array2, ($col, $ctr2) );
To
push( #array2, [$col, $ctr2] );
This makes an array reference, rather than a list. A list in this context just collapses down into regular arguments to push, meaning you're pushing two separate strings into #array2.
You are correct that your array of arrays is not an array of arrays, since in Perl there is no such thing. So what do you have instead? There's two ways to see.
First, when you print #array2, you come up with a string composed of alternating MACs and counts, separated by spaces. Since the spaces sort-of-signify the division between array elements, we might surmise that what we've got is a single array of heterogeneous elements, such that element 0 is a MAC, element 1 is a count, element 2 is another MAC, and so on.
The other perspective is to look at how #array2 is constructed:
push( #array2, ($col,$ctr2) );
From the documentation for push, we find that push ARRAY LIST works by appending the elements of LIST to the end of ARRAY. This has the effect of flattening the list into the array such that its original identity as a list is lost. You can add all the parentheses you want, when Perl expects a list it flattens all of them away.
So how do you achieve the effect you want? The List-of-Lists documentation has a detailed treatment, but the short answer is that you make a list of array references. Array references are scalars and are therefore legal elements in an array. But they retain their identify as array references!
The anonymous array reference constructor is the square bracket []. In order to push an array reference containing the elements $col and $ctr2 onto the end of #array2, you simply do this:
push( #array2, [$col, $ctr2] );
The code you wrote for accessing a particular element of the array-reference-in-an-array now works. But since I've already written a bunch of paragraphs on the subject, let me finish by explaining what was wrong originally and how changing the push statements suddenly makes it work.
The expression $array2[$ctr3][0] is sometimes written as $array2[$ctr3]->[0] to clarify what it's actually doing. What it does is to take the value of $array2[$ctr3] and treat it as an array reference, taking its 0 element. If we take $ctr3 to be 0 (as it would be at the top of the loop) the value of $array2[$ctr3] is the first element, 00:00:00:00:00:00. When you then subsequently ask Perl to treat 00:00:00:00:00:00 as an array reference, Perl dies because it doesn't know how to treat a string as an array reference.
When instead the value of $array2[$ctr3] is an array reference because that is what you pushed onto #array2 when constructing it, Perl is able to do as you ask, dereferencing the array reference and looking at element 0 of the resulting array, whose value happens to be 00:00:00:00:00:00.

Resources