Sorting Hash of Hashes by value - arrays

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

Related

Perl: find the max and min values in the array of hashes

I have the structure below in Perl:
#!/usr/bin/perl
use strict;
use warnings;
my %hash = (
'firstitem' => {
'1' => ["A","99"],
'2' => ["B","88"],
'3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"],
'4' => ["D","200"],
'5' => ["E","300"],
},
);
I am looking for a way to find the max number and min number in the array of each hash.
So the output will be
firstitem: max:99, min:77
seconditem: max:300, min:100
My idea is sorting the secondary key first and then do the bubble sort or other sorts in the for loop. It looks like not very elegant and smart.
foreach my $k1 (keys %hash) {
my $second_hash_ref = $hash{$k1};
my #sorted_k2 = sort { $a <=> $b } keys %{$second_hash_ref};
foreach my $i (0..$#sorted_k3){
#bubble sort or other sort
}
}
List::Util is a core module that provides the min and max functions:
use strict;
use warnings;
use List::Util qw(min max);
my %hash = (
'firstitem' => {
'1' => ["A","99"],
'2' => ["B","88"],
'3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"],
'4' => ["D","200"],
'5' => ["E","300"],
},
);
for my $key (keys(%hash)) {
my #numbers = map { $_->[1] } values(%{$hash{$key}});
printf("%s: max: %d, min: %d\n", $key, max(#numbers), min(#numbers));
}
Output:
firstitem: max: 99, min: 77
seconditem: max: 300, min: 100
You are almost there.
Once you get to the second level what you need is the whole list of numbers, so to be able to find max and min of the lot. Since you need the extreme values for all arrays within the subkey you don't need to iterate over the second-level keys.
Also, you can fetch all array content unless the number is guaranteed to be at the specific place. Then use grep with Scalar::Util::looks_like_number to filter out elements that aren't numbers.
Finally, there is no benefit in sorting the keys.
use warnings;
use strict;
use feature 'say';
use Scalar::Util 'looks_like_number';
use List::MoreUtils 'minmax';
my %hash = (
'firstitem' => {
'1' => ["A","99"], '2' => ["B","88"], '3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"], '4' => ["D","200"], '5' => ["E","300"],
},
);
foreach my $k1 (keys %hash)
{
my #nums =
grep { looks_like_number($_) }
map { #{ $hash{$k1}{$_} } }
keys %{$hash{$k1}};
my ($min, $max) = minmax #nums;
say "$k1: max = $max, min = $min";
}
This prints the expected values. If your real hash is exactly as shown you can extract arrayrefs directly by values %{$hash{$k1}}, with map { #$_ } to dereference them.
Since you need both extremes a good fit is minmax from List::MoreUtils, which is "the most efficient possible algorithm" for the job. The algorithm's performance is fully realized in the XS version of the module while the Perl version has some overhead.
Also note the core List::Util module, where individual min and max are, among other utilities.

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;

Perl reorder an array of hashes based on content

Have an array of hashes, I want to be able to reorder them, moving the first entry I find that matches a criteria to be the first entry in the array.
using List::Utils first method I can identify what I want to be the first entry in the array. How can I make the found entry the first element in the AoH?
#Borodin
An example of what the data looks like:
CAT1 => 'Foo', CAT2 => 'BAR', TITLE='test1',
CAT1 => 'BAZ', CAT2 => 'BAR', TITLE='test2',
.....
It has many entries. I wish to find the first entry (there could be more than one) where CAT1=BAZ and CAT2=BAR and move it to be the first item in the AoH.
Without realistic sample data it is hard to help.
You may sort the values of a list according to any criterion that is computable using Perl's sort operator, which takes an expression or a block as its second parameter
The library List::UtilsBy provides operators sort_by etc. that will probably provide a speed advantage if the sort criterion is a complex one
This sets up the data you've given and dumps it using Data::Dump
Then I've used first_index from List::MoreUtils, which finds the index of the first element of the array that conforms to your criteria
$_->{CAT1} eq 'BAZ' and $_->{CAT2} eq 'BAR'
And then an unshift together with a splice removes that element and puts it at the front of the array. There's a check that $i isn't zero to avoid moving an item that's already at the start of the array
Finally another call to dd shows that the matching item has been moved
use strict;
use warnings 'all';
use List::MoreUtils 'first_index';
use Data::Dump;
my #data = (
{
CAT1 => 'Foo',
CAT2 => 'BAR',
TITLE => 'test1',
},
{
CAT1 => 'BAZ',
CAT2 => 'BAR',
TITLE => 'test2',
}
);
dd \#data;
my $i = first_index {
$_->{CAT1} eq 'BAZ' and $_->{CAT2} eq 'BAR'
} #data;
die if $i < 0;
unshift #data, splice #data, $i, 1 unless $i == 0;
dd \#data;
output
[
{ CAT1 => "Foo", CAT2 => "BAR", TITLE => "test1" },
{ CAT1 => "BAZ", CAT2 => "BAR", TITLE => "test2" },
]
[
{ CAT1 => "BAZ", CAT2 => "BAR", TITLE => "test2" },
{ CAT1 => "Foo", CAT2 => "BAR", TITLE => "test1" },
]
To move the first matching entry to the start:
use List::MoreUtils qw( first_index );
my $i = first_index { matches($_) } #aoh;
unshift #aoh, splice(#aoh, $i, 1);
To move all matching entries to the start:
use sort 'stable';
#aoh =
sort {
my $a_matches = matches($a);
my $b_matches = matches($b);
( $a_matches ? 0 : 1 ) <=> ( $b_matches ? 0 : 1 )
}
#aoh;

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;

How do I reference a Perl hash in an array in a hash?

This is the code snippet I am working with:
my %photo_details = (
'black_cat' => (
('size' => '1600x1200', 'position' => -25),
('size' => '1280x1024', 'position' => 25),
('size' => '800x600', 'position' => 0),
),
'race_car' => (
('size' => '1600x1200', 'position' => 10),
('size' => '800x600', 'position' => 5),
),
);
my $photo = 'black_cat';
foreach my $photo_detail ($photo_details{$photo})
{
my $size = $photo_detail{'size'};
my $position = $photo_detail{'position'};
print ("size = $size, position = $position\n");
}
What I am expecting to get is:
size = 1600x1200, position = -25
size = 1280x1024, position = 25
size = 800x600, position = 0
What I do get is:
Use of uninitialized value $size in concatenation (.) or string at C:\Test.pl line 23.
Use of uninitialized value $position in concatenation (.) or string at C:\Test.pl line 23.
size = , position =
The foreach statement is clearly wrong as not only are there no values for $size and $position, it has only gone through the loop once instead of three times. I have tried all sorts of variants of variable prefixes and found none that work.
What am I doing wrong?
Here is some updated code, with an explanation below:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %photo_details = (
'black_cat' => [
{'size' => '1600x1200', 'position' => -25},
{'size' => '1280x1024', 'position' => 25},
{'size' => '800x600', 'position' => 0},
],
'race_car' => [
{'size' => '1600x1200', 'position' => 10},
{'size' => '800x600', 'position' => 5},
],
);
print Dumper( %photo_details );
foreach my $name ( keys %photo_details ) {
foreach my $photo_detail ( #{ $photo_details{$name} } ) {
my $size = $photo_detail->{'size'};
my $position = $photo_detail->{'position'};
print Dumper( $photo_details{$photo} );
print ("size = $size, position = $position\n");
}
}
I've replaced some of your parentheses with square and curly brackets. In Perl, square brackets give you a reference to an anonymous array, and curly brackets denote a reference to an anonymous hash. These are called anonymous because there's no explicit variable name for the anonymous array or hash.
As Perl data structures make you store a reference to a hash rather than the actual hash, you need these to construct the references. You can do this in two steps like this:
my #array = ( 1, 2, 3 );
my $array_ref = \#array;
my %hash = ( 'one' => 1, 'two' => 2, 'three' => 3 );
my $hash_ref = \%hash_ref;
To get data out of $array_ref and $hash_ref, you need the -> operator:
print $array_ref->[0], "\n";
print $hash_ref->{one}, "\n";
You don't need the quotes inside of the {} when referencing a hash key, although some people consider quotes on a hash key to be good practice.
I added an example of iteration over the entire data structure as an example rather than just looking at one reference. Here's the first line:
foreach my $name ( keys %photo_details ) {
The keys method returns all of the keys in a hash, so that you can get them in order. The next line iterates over all of the photo_detail hashrefs in %photo_details:
foreach my $photo_detail ( #{ $photo_details{$photo} } ) {
The #{ $photo_details{$photo} } de-references the reference $photo_details{$photo} into an array, which you can iterate over it with foreach.
The last thing that I added is a call to Data::Dumper, a very useful module distributed with Perl that prints out data structures for you. This is very handy when building up data structures like this, as is its closely related cousin Data::Dumper::Simple. This module is unfortunately not distributed with Perl, but I prefer its output as it includes variable names.
For some further reading about how to build up complex data structures using references, check out perlreftut.
First of all, always start every script or module with:
use strict;
use warnings;
You will get more warning messages and sooner, which greatly helps debugging.
I cannot duplicate your error: when I put that code into a file and run it with no additional flags, I get: size = , position =. There is no $size variable in the code you printed, so the error message does not match.
Nevertheless, you are declaring your data structures incorrectly. Hashes and arrays can
only contain scalar values, not lists: so if you want to nest an array or
a hash, you need to make it a reference. See perldoc perldata, perldoc perldsc
and perldoc perlreftut for more about data structures and references.
my %photo_details = (
black_cat => [
{ size => '1600x1200', position => -25 },
{ size => '1280x1024', position => 25 },
{ size => '800x600', position => 0 },
],
race_car => [
{ size => '1600x1200', position => 10 },
{ size => '800x600', position => 5 },
],
);
foreach my $photo_detail (#{$photo_details{black_cat}})
{
my $size = $photo_detail->{size};
my $position = $photo_detail->{position};
print ("size = $size, position = $position\n");
}
There's really only one thing you have to worry about, and that's the top level of the data structure. After that, you just use the right indexing syntax for each level:
If you have a regular hash, you access the key that you want then line up the additional indices for each level after it:
%regular_hash = ...;
$regular_hash{$key}[$index]{$key2};
If you have a reference, you do almost the same thing, but you have to start off with the initial dereference with an arrow, ->, after the top-level reference. After that it's the same indexing sequence:
$hash_ref = ...;
$hash_ref->{$key}[$index]{$key2};
For all of the details, see Intermediate Perl where we explain reference syntax.

Resources