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.
Related
How simply convert a hash to an array of key/value ?
my %h;
%h{1} = 11;
%h{2} = 22;
and I want an array #result which I could represent as : [ { 1 => 11 }, { 2 => 22} ] (yes like in json to be clear)
That's an easy one.
my #h = map { { $_ => $h{$_} } } keys %h;
You could also use the built in List::Util library's excellent pairmap function.
use List::Util qw< pairmap >;
my #array_of_pairs = pairmap { { $a => $b } } %some_hash;
Even if you don't use this function today, take some time to check out List::Util, there is a lot of good stuff in there.
I'd like to give the user the possibility to change the sorting order (asc / desc) in a data structure. As far as I know, this is done changing the order of $a and $bin the code, but I'd like to programmatically change this to avoid redundant code.
I made a working example:
use 5.018;
use warnings;
# Supply any argument to change sorting order
my $sorting_direction = $ARGV[0];
my $data = {
'item1' => {
'min' => 4,
'size' => 825,
'max' => 256,
},
'item2' => {
'min' => 4,
'size' => 130,
'max' => 65,
},
};
if (defined $sorting_direction) {
foreach my $item (sort { $$data{$a}{'size'} <=> $$data{$b}{'size'} } keys %{$data} ) {
say "$item\t", $$data{$item}{'size'};
}
} else {
foreach my $item (sort { $$data{$b}{'size'} <=> $$data{$a}{'size'} } keys %{$data} ) {
say "$item\t", $$data{$item}{'size'};
}
}
Giving any parameter will change the sorting_direction. Can I do this without the if conditional?
As <=> has a value of -1, 0 or 1, you can multiply with -1 to get the opposite sorting order.
So if your $sorting_direction is 1 or -1 use
$sorting_direction * ( $$data{$a}{'size'} <=> $$data{$b}{'size'} )
A generic solution is to use different compare functions.
my %sorters = (
by_size_asc => sub { $data->{$a}{size} <=> $data->{$b}{size} },
by_size_desc => sub { $data->{$b}{size} <=> $data->{$a}{size} },
# ...
);
#ARGV
or die("usage\n");
my $sorter = $sorters{$ARGV[0]}
or die("Invalid sort function \"$ARGV[0]\".\n");
my #sorted_keys = sort $sorter keys(%$data);
You could also use different sort functions, such as when using the great Sort::Key module.
use Sort::Key qw( ikeysort rikeysort );
my %sorters = (
by_size_asc => sub { ikeysort { $data->{$_}{size} } #_ },
by_size_desc => sub { rikeysort { $data->{$_}{size} } #_ },
# ...
);
#ARGV
or die("usage\n");
my $sorter = $sorters{$ARGV[0]}
or die("Invalid sort function \"$ARGV[0]\".\n");
my #sorted_keys = $sorter->( keys(%$data) );
While it's always going to be slower because it's a full extra operation, if performance is not as much a concern as code cleanliness you could just reverse the list when the opposite sorting direction is chosen. Note that this would be slightly different in the case of sorting equal elements, as sort in Perl is normally stable (equal elements stay in the same order they originally were).
my #sorted = sort { $$data{$a}{'size'} <=> $$data{$b}{'size'} } keys %{$data};
#sorted = reverse #sorted if $reverse;
I am looking to retrieve the highest value from the array in the category price, and then double that value and save it in the array. My logic told me to write the following piece of code:
#!/usr/bin/perl
use List::Util qw(min max);
my #array = (
{clothing => "Trouser", price => 40, Quantity => 2},
{clothing => "Socks", price => 5, Quantity => 12},);
my $maxi = max(#array);
$name = $ {$maxi} { price };
$name = $name * 2;
print $name;
This code seems to randomly toggle between the two values. How do I get the maximum value from the category stated? And how do I take the new value and replace it with the old one?
The max function from List::Util operates on a list of numbers, not of complex data structures.
You can extract the prices from the structures with map:
my #prices = map $_->{price}, #array;
my $max = max(#prices);
To find the corresponding structure, use grep:
my #maximal = grep $_->{price} == $max, #array;
$_->{price} *= 2 for #maximal;
(I used an array, as there can be more than one such elements).
Or, don't use max, cycle over the array yourself:
my #max_idx = 0;
for my $i (1 .. $#array) {
my $cmp = $array[$i]{price} <=> $array[ $max_idx[0] ]{price};
if ($cmp >= 0) {
#max_idx = () if $cmp;
push #max_idx, $i;
}
}
$_->{price} *= 2 for #array[#max_idx];
The reason this isn't doing what you think is because there's no such thing as an array of hashes - there's an array of hash references.
So if you:
print join ( "\n", #array );
You get:
HASH(0x5a6dec)
HASH(0x133ef4c)
max will not give you the right answer based on that.
What you need to do is access the subelement of your hash. I would suggest the easiest way is by sorting your array by price:
my #sorted = sort { $b -> {price} <=> $a -> {price} } #array;
Then - your 'highest' price is in the first slot of the array:
print $sorted[0] -> {price};
Which you can then manipulate:
print $sorted[0] -> {price},"\n";
$sorted[0] -> {price} *= 2;
use Data::Dumper;
print Dumper \#sorted;
max function "returns the entry in the list with the highest numerical value" (perldoc). Your list consists of hash references that do not have any inherent numerical value, co max cannot compare them. Unfortunately, you cannot provide your own predicate to max function. However, you can use reduce function, that can be seen as more general version of min, max and others.
Once you get maximum element, you can simply multiply one of its attributes by two (or any other factor).
#!/usr/bin/perl
use List::Util qw(reduce);
my #array = (
{clothing => "Trouser", price => 40, Quantity => 2},
{clothing => "Socks", price => 5, Quantity => 12}
);
my $max = reduce { $a->{price} > $b->{price} ? $a : $b } #array;
$max->{price} *= 2;
print $max->{price};
What you are really looking for is not the maximum, but rather an argmax.
In your case, price is a function that maps each item to its price. You want to find which element(s) have the highest price.
Not which hashref has the highest numeric address. You may be tempted to sort the array of hashrefs by price, but avoid that unless it is needed for some other reason because that does too much work.
Instead, first find the highest price, then grep all elements which have price equal to the highest price to find all items with the highest price (while the maximum is unique, there can always be more than one maximizer. That is, the cardinality of the pre-image of { maximum } need not be 1.
#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw(max);
my #items = (
{clothing => "Trouser", price => 40, Quantity => 2},
{clothing => "Socks", price => 5, Quantity => 12},
{clothing => "Shirt", price => 40, Quantity => 1},
{clothing => "Hat", price => 10, Quantity => 3},
);
my $max_price = max map $_->{price}, #items;
my #argmax = grep $_->{price} eq $max_price, #items;
print "Items with highest price\n";
print "#{[ #{ $_ }{qw( clothing price Quantity )} ]}\n" for #argmax;
Output:
Items with highest price
Trouser 40 2
Shirt 40 1
Given that you want to change the prices of items with the highest price, you actually want to grep the set of indexes.
Therefore, change the last part to use indexes:
my $max_price = max map $_->{price}, #items;
my #argmax = grep $items[$_]->{price} eq $max_price, 0 .. $#items;
$items[$_]->{price} *= 2 for #argmax;
print "Items with highest price\n";
print "#{[ #{ $items[$_] }{qw( clothing price Quantity )} ]}\n" for #argmax;
Output:
Items with highest price
Trouser 80 2
Shirt 80 1
List::Util::max doesn't return index of the maximal element and works only on numeric scalars. So you have to help yourselvs.
use strict;
use warnings;
my #array = (
{ clothing => "Trouser", price => 40, Quantity => 2 },
{ clothing => "Socks", price => 5, Quantity => 12 },
);
my $maxi = 0;
for my $i ( 1 .. $#array ) {
$maxi = $i if $array[$i]{price} > $array[$maxi]{price};
}
$array[$maxi]{price} *= 2;
for my $rec ($array[$maxi]) {
print qq/{ ${\(join ', ', map "$_ => $rec->{$_}", sort keys %$rec)} }\n/;
}
If you are looking for more comfort and weird new syntax.
use strict;
use warnings;
use Carp;
my #array = (
{ clothing => "Trouser", price => 40, Quantity => 2 },
{ clothing => "Socks", price => 5, Quantity => 12 },
);
sub maxi (#) {
croak "Empty array" unless #_;
my $maxi = 0;
for my $i ( 1 .. $#_ ) {
$maxi = $i if $_[$i] > $_[$maxi];
}
return $maxi;
}
my $maxi = maxi map $_->{price}, #array;
print $maxi, "\n";
$array[$maxi]{price} *= 2;
for my $rec ( $array[$maxi] ) {
print qq/{ ${\(join ', ', map "$_ => $rec->{$_}", sort keys %$rec)} }\n/;
}
And finally you can use List::Util::max as with map and grep because all your array members are pointers to an anonymous hash so you can modify its content unless you want change them to some other type.
use strict;
use warnings;
use List::Util qw(max);
my #array = (
{ clothing => "Trouser", price => 40, Quantity => 2 },
{ clothing => "Socks", price => 5, Quantity => 12 },
{ clothing => "Shirt", price => 40, Quantity => 1 },
{ clothing => "Hat", price => 10, Quantity => 3 },
);
sub format_item {
my $item = shift;
local $" = ', '; # " for broken syntax highliter
return qq({ #{[map "$_ => $item->{$_}", sort keys %$item]} });
}
my $maxprice = max map $_->{price}, #array;
my #maxs = grep $_->{price} == $maxprice, #array;
# double prices
$_->{price} *= 2 for #maxs;
print format_item($_), "\n" for #maxs;
# if you are interested in the only one
my ($max_item) = #maxs;
print format_item($max_item), "\n";
Can anybody tell me what I am doing wrong here? I have tried just about every possible combination of array / hash type and sort query I can think of and cannot seem to get this to work.
I am trying to sort the hash ref below by value1 :
my $test = {
'1' => { 'value1' => '0.001000', 'value2' => 'red'},
'2' => { 'value1' => '0.005000', 'value2' => 'blue'},
'3' => { 'value1' => '0.002000', 'value2' => 'green'},
'7' => { 'value1' => '0.002243', 'value2' => 'violet'},
'9' => { 'value1' => '0.001005', 'value2' => 'yellow'},
'20' => { 'value1' => '0.0010200', 'value2' => 'purple'}
};
Using this sort loop:
foreach (sort { $test{$a}->{'value1'} <=> $test{$b}->{'value1'} } keys \%{$test} ){
print "key: $_ value: $test->{$_}->{'value1'}\n"
}
I get:
key: 1 value: 0.001000
key: 3 value: 0.002000
key: 7 value: 0.002243
key: 9 value: 0.001005
key: 2 value: 0.005000
key: 20 value: 0.0010200
I have tried with integers and the same thing seems to happen.
I don't actually need to loop through the hash either I just want it ordered for later use. Its easy to do with an array of hashes, but not so with a hash of hashes..?
Don't call keys on a reference. Call it on the actual hash.
Also, this $test{$a}->, should be $test->{$a}, because $test is a hash reference.
foreach (sort { $test->{$a}{'value1'} <=> $test->{$b}{'value1'} } keys %{$test} ){
print "key: $_ value: $test->{$_}->{'value1'}\n"
}
If you had use strict; and use warnings; turned on, you would've gotten the following error to alert you to an issue:
Global symbol "%test" requires explicit package name
Just wanted to provide a source for the other answers, and a working code example. Like they said, you are calling keys with a hash reference for the argument. According to the documentation:
Starting with Perl 5.14, keys can take a scalar EXPR, which must
contain a reference to an unblessed hash or array. The argument will
be dereferenced automatically. This aspect of keys is considered
highly experimental. The exact behaviour may change in a future
version of Perl.
for (keys $hashref) { ... }
for (keys $obj->get_arrayref) { ... }
However this does work for me:
#!/usr/bin/perl
use strict;
use warnings;
my $test = {
'1' => { 'value1' => '0.001000', 'value2' => 'red'},
'2' => { 'value1' => '0.005000', 'value2' => 'blue'},
'3' => { 'value1' => '0.002000', 'value2' => 'green'},
'7' => { 'value1' => '0.002243', 'value2' => 'violet'},
'9' => { 'value1' => '0.001005', 'value2' => 'yellow'},
'20' => { 'value1' => '0.0010200', 'value2' => 'purple'}
};
foreach (sort { $test->{$a}->{'value1'} <=> $test->{$b}->{'value1'} } keys \%{$test} ) {
print "key: $_ value: $test->{$_}->{'value1'}\n"
}
Example:
matt#mattpc:~/Documents/test/10$ perl test.pl
key: 1 value: 0.001000
key: 9 value: 0.001005
key: 20 value: 0.0010200
key: 3 value: 0.002000
key: 7 value: 0.002243
key: 2 value: 0.005000
matt#mattpc:~/Documents/test/10$ perl --version
This is perl 5, version 14, subversion 2 (v5.14.2) built for x86_64-linux-gnu-thread-multi
(with 88 registered patches, see perl -V for more detail)
This is with using a hash reference as the input to keys which I would not recommend.
I'd recommend following the advice of the other questions and adding use strict and use warnings and changing the hash reference to a hash, %{test}.
It's simply keys %$test. The argument of keys must be a hash, not a hashref. \%${test} is the same as $test, a ref.
And use $test->{$a}, not $test{$a}, as $test is a hash-ref, not a hash.
foreach (sort { $test->{$a}->{'value1'} <=> $test->{$b}->'{value1'} } keys %$test) {
print "key: $_ value: $test->{$_}->{'value1'}\n"
}
or a shorter form with some syntactic sugar: You can omit the additional arrows after the first one. And you don't have to quote string literal keys when addressing hashes.
foreach (sort { $test->{$a}{value1} <=> $test->{$b}{value1} } keys %$test) {
print "key: $_ value: $test->{$_}{value1}\n"
}
It usually helps a lot to turn on use warnings;, at least for debugging.
The only wrong thing I can spot is usage of hash ref \%{$test} where you should use hash %$test. keys work with that.
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']
];