Changing value of an array in Perl - arrays

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";

Related

Programmatically change sorting order in Perl

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;

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.

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;

Generating a unordered list hash from a array

I am trying to use hashes to generate an unordered list that i can further use in a jstree. But this array has to be generated only from an array that has been passed thru .
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
I want the output to look something like
$data = {
"New Order" => {
"Recurring Order" =>{
Previously cancelled Order = 1
}
}
};
I can simply do
my $data{$array[0]}{$array[1]}{$array[2]} = 1
but the array can be of n variables, so it becomes a bit more complicated than that. I am thinking of recursion, but i have been sitting here for the last hour trying to figure that out
This will generate the data structure as you have defined it. Not sure why you'd want it though.
my #input = ( "New Order","Recurring Order","Previously Cancelled Order");
my $data = 1;
$data = {$_ => $data} for reverse #input;
use Data::Dump;
dd $data;
If you're just wanting to randomize your array, then use List::Util;
use List::Util qw(shuffle);
my #newOrder = shuffle #input;
sub recursive {
my $v = shift #_;
return #_>1 ? { $v => recursive(#_) } : { $v => #_ };
}
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
use Data::Dumper; print Dumper recursive(#array, 1);
output
$VAR1 = {
'New Order' => {
'Recurring Order' => {
'Previously Cancelled Order' => 1
}
}
};

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

Resources