Assign arrays to hash subkeys - arrays

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;

Related

Iterative Hash Set up

I have the following array...
my #array=("100 2", "300 1", "200 3");
From this array I want to iteratively construct a hash.
Current Script:
my %hash;
foreach (#array) {
my #split = (split /\s+/, $_);
%hash = ("$split[0]", "$split[1]");
}
Current Output:
$VAR1 = {
'200' => '3'
};
This is not what I want. My goal is...
Goal Output:
$VAR1 = {
'100' => '2'
'300' => '1'
'200' => '3'
};
What do I need to do?
I am using: Perl 5, Version 18
Assigning to a hash—something you are doing each pass of the loop—replaces its contents. Replace
%hash = ("$split[0]", "$split[1]");
with
$hash{$split[0]} = $split[1];
Alternatively, replace everything with
my %hash = map { split } #array;

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'} } );

perl: deep merge with per-element arrays merge

I'm trying to merge two hashes and Hash::Merge does almost exactly what I need, except for arrays. Instead of concatenating arrays I need it to do per-element merge.
For example:
use Hash::Merge qw (merge);
my %a = ( 'arr' => [ { 'a' => 'b' } ] );
my %b = ( 'arr' => [ { 'c' => 'd' } ] );
my %c = %{ merge( \%a, \%b) };
Desired result is ('arr'=>[{'a'=>'b','c'=>'d'}]), actual result is ('arr'=>[{'a'=>'b'},{'c'=>'d'}])
Can this be done by using specify_behavior or is there some other way?
I think that specify_behaviour is used to specify how to handle conflicts, or uneven structures to merge. The documentation doesn't actually say much. But try it, go through defined shortcuts, or try to set them yourself. For your data structure you could try
SCALAR => ARRAY => sub { [ %{$_0}, %{$_[0]} ] }
SCALAR => ARRAY => HASH => sub { [ $_[0], $_[0] ] }
If you tried and it didn't work you may have found a bug in the module? By what you show it just didn't go "deep" enough. Here it is without the module. I've enlarged your sample structures.
use warnings;
use strict;
my %a = (
'arr1' => [ { a => 'A', a1 => 'A1' } ],
'arr2' => [ { aa => 'AA', aa1 => 'AA1' } ]
);
my %b = (
'arr1' => [ { b => 'B', b1 => 'B1' } ],
'arr2' => [ { bb => 'BB', bb1 => 'BB1' } ]
);
# Copy top level, %a to our target %c
my %c;
#c{keys %a} = values %a;
# Iterate over hash keys, then through array
foreach my $key (sort keys %c) {
my $arr_len = #{$c{$key}};
foreach my $i (0..$arr_len-1) {
my %hb = %{ ${$b{$key}}[$i] };
# merge: add %b to %c
#{ ${$c{$key}}[$i] }{keys %hb} = values %hb;
}
}
# Print it out
foreach my $key (sort keys %c) {
print "$key: ";
my $arr_len = #{$c{$key}};
foreach my $i (0..$arr_len-1) {
my %hc = %{ ${$c{$key}}[$i] };
print "$_ => $hc{$_}, " for sort keys %hc;
}
print "\n";
}
This prints the contents of %c (aligned manually here)
arr1: a => A, a1 => A1, b => B, b1 => B1,
arr2: aa => AA, aa1 => AA1, bb => BB, bb1 => BB1,
Code does not handle arrays/hashes of unequal sizes but checks can be added readily.
Another solution (that handles uneven hash elements in %a and %b).
my %c;
foreach my $key (keys %a, keys %b) {
my $a_ref = $a{$key};
my $b_ref = $b{$key};
$c{$key} = { map %$_, #$a_ref, #$b_ref };
}
use Data::Dumper;
print Dumper \%c;

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']
];

Can references be made without declaring a variable first?

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']];

Resources