Can references be made without declaring a variable first? - arrays

I have this code that works
my #new = keys %h1;
my #old = keys %h2;
function(\#new, \#old);
but can it be done without having to declare variables first?
function must have its arguments as references.

use strict;
use Data::Dumper;
my %test = (key1 => "value",key2 => "value2");
my %test2 = (key3 => "value3",key4 => "value4");
test_sub([keys %test], [keys %test2]);
sub test_sub{
my $ref_arr = shift;
my $ref_arr2 = shift;
print Dumper($ref_arr);
print Dumper($ref_arr2);
}
Output:
$VAR1 = [
'key2',
'key1'
];
$VAR1 = [
'key4',
'key3'
];

function([ keys %h1 ], [ keys %h2 ]);
From perldoc perlref:
A reference to an anonymous array can
be created using square brackets:
$arrayref = [1, 2, ['a', 'b', 'c']];

Related

Passing an array to a subroutine encloses it in another array?

I noticed that when I pass an array to my subroutine it seems like it gets encapsulated by another array (so two levels, while the initial is only one).
I know that using references to arrays is better, but I'm wondering in this specific case why it is not working as expected.
Code example:
#!/usr/local/bin/perl
use Data::Dumper;
sub testSub {
my (#arr) = (#_);
print Dumper \#arr;
}
my #testArray = ();
push #testArray, {
'key1' => 'value1',
'key2' => 'value2',
'urls' => [ 'www.example.com' ]
};
print Dumper #testArray;
foreach my $item ( #testArray ) {
my #urls = testSub( $item->{'urls'} );
}
output
$VAR1 = {
'urls' => [
'www.example.com'
],
'key1' => 'value1',
'key2' => 'value2'
};
$VAR1 = [
[
'www.example.com'
]
];
my #urls = testSub( $item->{'urls'}, 'abc' );
Result of Dumper in subrotine:
$VAR1 = [
[
'www.example.com'
],
'abc'
];
Array passed by reference. Since at the time of compilation perl did not know what will be in the scalar $item->{'urls'}.
my #urls = testSub( #{ $item->{'urls'} }, 'abc' );
Result of Dumper in subrotine:
$VAR1 = [
'www.example.com',
'abc'
];
Now the compiler expects an array and turns it into a list.
You are passing $item->{'urls'} to your subroutine
Your Data::Dumper output clearly shows that the hash element looks like this
'urls' => [ 'www.example.com' ]
When you call testSub, you are making an assignment that is equivalent to
my #arr = ( [ 'www.example.com' ] );
Your statement print Dumper \#arr passes an array reference to Dumper, so it displays
[ [ 'www.example.com' ] ]
It would help your confusion if you were consistent in calling Dumper. print Dumper #testArray passes the contents of #testArray as individual parameters (although in this case the array has only a single element) while print Dumper \#arr passes an array reference as a single parameter, and is the better choice
If you want testSub to receive a list of URLs, you must expand the array $item->{urls} into a list with #{ ... }:
my #urls = testSub( #{ $item->{'urls'} } );

Assign arrays to hash subkeys

Given an array of keys and an array of values, I can create a hash with these keys and values using #hash{#keys} = #vals.
However, I would like to do this for subkeys of a hash. This does not work: $h{"key"}{#subkeys} = #vals.
$ perl -MData::Dumper -le '
#subkeys=(qw(one two));
#vals=(1, 2);
$hash{"key"}{#subkeys} = #vals;
for (qw(subkeys vals)) {
print "$_ :\n", Dumper(\#{$_})
};
print "hash: \n", Dumper(\%hash);'
What I get is:
subkeys :
$VAR1 = [
'one',
'two'
];
vals :
$VAR1 = [
1,
2
];
hash:
$VAR1 = {
'key' => {
'2' => 2
}
};
If this is possible, what would be the correct syntax to get the following Dumper result:
$VAR1 = {
'key' => {
'one' => 1,
'two' => 2
}
};
It does work when using a temporary hash:
perl -MData::Dumper -le '#subkeys=(qw(one two)); #vals=(1, 2); #tmp{#subkeys}=#vals; $hash{"key"}={%tmp}; print Dumper(\%hash)'
But I suspect I'm just missing the correct syntax to get it without the %tmp hash.
You need to close the hashref part in a #{} slice "cast".
#{$hash{"key"}}{#subkeys} = #vals;

Perl multi hash from list

I have a list with some values which are connected. I need to create a hashmap with keys and values from the list and merge together. But i don't really know how to do it.
Input:
my #in =(
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat');
Expected output:
"{ $env => { $ver => [ $file1, $file2, ... ] } }"
I've tried these:
(1)
my #sack_files = (
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat');
my $sack_tree = {};
my %hash=();
for( my $i=0; $i<scalar #sack_files; $i++){
my #array = split(/[\/]+/,$sack_files[$i]);
for(my $i=0;$i<(scalar #array)-1;$i++){
my $first = $array[$i];
my $second = $array[$i+1];
$hash{$first}=$second;
}
# merge
}
(2)
use Data::Dumper;
my #sack_files = (
'mgenv/1_2_3/parent.dx_environment',
'mgenv/1_2_3/doc/types.dat',
);
my $sack_tree = {};
my %hash=();
for( my $i=0; $i<scalar #sack_files; $i++){
my #array = split(/[\/]+/,$sack_files[$i]);
nest(\%hash,#array);
}
In the second case I get an error because when the loop variable i=1 ,the key/values already exists so maybe i have to check the previously added key/values. But I don't really know how.
I would really appreciate any ideas.
Just use push to add new members to an existing array in a hash of hashes. You have to dereference the array reference with #{ ... }.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #sack_files = qw( mgenv/1_2_3/parent.dx_environment
mgenv/1_2_3/doc/types.dat
mgenv/1_2_3/doc/etc.dat
mgenv/4_5_6/parent.dx_environment
mgenv/4_5_6/doc/types.dat
u5env/1_2_3/parent.dx_environment
u5env/1_2_3/doc/types.dat
u5env/4_5_6/parent.dx_environment
u5env/4_5_6/doc/types.dat
);
my %hash;
for my $sack_file (#sack_files) {
my ($env, $ver, $file) = split m{/}, $sack_file, 3;
push #{ $hash{$env}{$ver} }, $file;
}
print Dumper \%hash;
output
$VAR1 = {
'mgenv' => {
'1_2_3' => [
'parent.dx_environment',
'doc/types.dat',
'doc/etc.dat'
],
'4_5_6' => [
'parent.dx_environment',
'doc/types.dat'
]
},
'u5env' => {
'4_5_6' => [
'parent.dx_environment',
'doc/types.dat'
],
'1_2_3' => [
'parent.dx_environment',
'doc/types.dat'
]
}
};

Sorting Hash of Hashes by value

I have the following data structure
my %HoH = {
'foo1' => {
'bam' => 1,
'zip' => 0,
},
'foo2' => {
'bam' => 0,
'zip' => 1,
'boo' => 1
}
};
I would like to sort KEY1 (foo1 or foo2) by the VALUE stored in 'zip' in order from greatest to least.
Here's how I'm doing it.
use strict; use warnings;
use Data::Dumper;
my #sorted;
foreach my $KEY1 (keys %HoH) {
# sort KEY1 by the value 'zip' maps to in descending order
#sorted = sort {$HoH{$KEY1}{'zip'}{$b} <=>
$HoH{$KEY1}{'zip'}{$a}} keys %HoH;
}
print Dumper(\#sorted);
I'm getting an weird warning: Reference found where even-sized list expected at test.pl line 6.
Also print Dumper(\#sorted); is printing
$VAR1 = [
'HASH(0x1b542a8)'
];
When it should be printing
$VAR1 = [
['foo2', 'foo1']
];
Since foo2 has 1 zip and foo1 has 0 zip.
%HoH is declared as a hash, but is defined as a hashreference. Use parentheses (...) instead of braces {...}.
You don't need to loop through the hash to sort it. Sort will take care of that.
if you sort {...} keys %HoH, then the special variables $a and $b represent the keys of %HoH as it performs the sort.
$a and $b are in reverse order because your expected result is in decreasing order. (Update: Oh I just noticed that you had that in the first place.)
The zip value in the nested hash is $HoH{$KEY}{'zip'}, which is what you should sort by.
use strict;
use warnings;
use Data::Dumper;
my %HoH = (
'foo1' => {
'bam' => 1,
'zip' => 0,
},
'foo2' => {
'bam' => 0,
'zip' => 1,
'boo' => 1
}
);
my #sorted = sort {$HoH{$b}{'zip'} <=> $HoH{$a}{'zip'}} keys %HoH;
print Dumper \#sorted;
Note that the result of this code will give you an array:
$VAR1 = [
'foo2',
'foo1'
];
... not a nested array:
$VAR1 = [
['foo2', 'foo1']
];

How do I iterate through a reference to an array of hashes in Perl?

I have a reference to an array of hases that I pass to a subroutine in my perl script
This is the code:
sub mySub {
(my $resultref) = #_;
my #list = #$resultref;
print Dumper(#list);
foreach my $result (#list) {
print Dumper($result);
}
}
And this is the output:
$VAR1 = [
{
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
},
{
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
},
];
$VAR1 = [
{
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
},
{
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
},
];
The loop is putting the whole array into the $result variable. I have tried dereferencing it as #$result[0] with no success.
How do I loop those hashes individually?
Thanks!
The arguments to Data::Dumper's Dumper function should be references. E.g.:
use Data::Dumper;
my #array = ([1,2,3], [11,22,33]); # Two-dimensional array
print Dumper #array; # print array
print Dumper \#array; # print reference to array
The output:
$VAR1 = [
1,
2,
3
];
$VAR2 = [
11,
22,
33
];
$VAR1 = [
[
1,
2,
3
],
[
11,
22,
33
]
];
The second print gives us the entire structure in one variable. When you print the array directly, it expands into all its elements, so...
print Dumper #array;
Is equivalent to:
print Dumper $array[0], $array[1], ..., $array[$#array];
So, in your case, just do:
sub mySub {
my ($resultref) = #_;
print Dumper $resultref;
}
Accessing the inner variables:
Just take a look at Data::Dumper's output:
$VAR1 = [ # bracket denotes start of an array ref
{ # curly brackets = hash ref
'portName' => '1.1',
'ips' => [
'192.168.1.242'
],
'switchIp' => '192.168.1.20',
'macs' => [
'00:16:76:9e:63:47'
]
}, # hash ref ends, comma = new array element begins
{ # new hash ref
'portName' => '1.10',
'ips' => [
'192.168.1.119',
'192.168.1.3'
],
'switchIp' => '192.168.1.20',
'macs' => [
'd0:67:e5:f8:7e:7e',
'd0:67:e5:f8:7e:76'
]
}, # end of hash
]; # end of array
Important to note here is that all elements of an array, and all the values of a hash are scalars. Therefore, all hashes and arrays can easily be broken up into a list of scalars.
for my $aref (#$resultref) { # starting array ref
for my $aref2 (#$aref) { # second level array ref
for my $href (#$aref2) # here begins the hash
local $\ = "\n"; # add newline to print for simplicity
print $href->{portName}; # printing a scalar
print for #{$href_>{ips}}; # printing an array ref w post-script loop
print $href->{switchIp};
print for #{$href->{macs}};
}
}
}
Note the use of the arrow operator to dereference a reference. If you have a hash or array you would do $array[0] or $hash{$key}, but by using a reference, you "point" to the address contained in the reference instead: $array->[0] or $hash->{$key}.
The parameter passed to mySub is a reference to an array of arrayrefs. To iterate over the nested arrays you could do:
sub mySub {
my ($resultref) = #_;
for my $result (#$resultref) {
my #list = #$result; # array of hashrefs
...
}
}
I have a reference to an array of hases
No, you've passed in a reference to an array of references to an array of hashes.
If you remove that outer level of indirection then your code works as desired.

Resources