Sorting Array in Perl - arrays

I thought I could do it the way that has been stated below. However when I sort it this way the output is the values in hexadecimal values, instead of the string pointing to "item" in the array #menu. What I want to achieve is to sort it by "item-name"
my #menu = (
{ item => "Blazer", price => 100, color => "Brown" },
{ item => "Jeans", price => 50, color => "Blue" },
{ item => "Shawl", price => 30, color => "Red" },
{ item => "Suit", price => 40, color => "Black" },
{ item => "Top", price => 25, color => "White" },
);
my #test = sort {item } #menu;
foreach (#test){
print $_;
}

Your print $_ prints the string value of each hash reference, so you will get something like HASH(0x1d33524). You need to print the fields of each hash that you're interested in.
Also, you need a proper comparison expression inside the sort block. Just giving the name of a hash key won't do anything useful.
use strict;
use warnings;
my #menu = (
{ item => 'Blazer', price => 100, color => 'Brown' },
{ item => 'Jeans', price => 50, color => 'Blue' },
{ item => 'Shawl', price => 30, color => 'Red' },
{ item => 'Suit', price => 40, color => 'Black' },
{ item => 'Top', price => 25, color => 'White' },
);
my #test = sort { $a->{item} cmp $b->{item} } #menu;
for ( #test ) {
print "#{$_}{qw/ item price color /}\n";
}
output
Blazer 100 Brown
Jeans 50 Blue
Shawl 30 Red
Suit 40 Black
Top 25 White
Update
If all you want is a sorted list of the item field values then you can write this more simply
use strict;
use warnings;
my #menu = (
{ item => 'Blazer', price => 100, color => 'Brown' },
{ item => 'Jeans', price => 50, color => 'Blue' },
{ item => 'Shawl', price => 30, color => 'Red' },
{ item => 'Suit', price => 40, color => 'Black' },
{ item => 'Top', price => 25, color => 'White' },
);
my #test = sort map { $_->{item} } #menu;
print "$_\n" for #test;
output
Blazer
Jeans
Shawl
Suit
Top

The contents of the curlies needs to be an expression that returns the whether the elements in $a should appear before the element in $b in the final result.
The elements, in this case, are references to hashes. You want to compare the item element of those hashes, so
sort { $a->{item} cmp $b->{item} }

The first argument to sort BLOCK LIST is the block that compares two members of the list, not the way how to extract the things to compare. See sort.
my #test = sort { $a->{item} cmp $b->{item} } #menu;
Sort::Key allows you to specify "what to sort by", not "how to compare elements".
use Sort::Key qw{ keysort };
# ...
my #test = keysort { $_->{item} } #menu;
In your code without strict, the string "item" is used to compare the elements, which doesn't really change the order in any way. What you see in the output is the representation of the members of the array, i.e. hash references. If you want to see the items only, use
for (#test) {
print $_->{item}, "\n";
}

See also List::UtilsBy:
use List::UtilsBy 'sort_by';
my #test = sort_by { $_->{item} } #menu;

Related

Iterate over an array of hashes and add to the value of specific hash values

If you have an array of hashes such as:
t = [{'pies' => 1}, {'burgers' => 1}, {'chips' => 1}]
what would be an efficient and readable way to add 1 to the value of a hash that has a particular key such as 'pies'?
Here's one way to increment the value(s) of an array's hashes based on a desired key:
t = [{ 'pies' => 1 }, { 'burgers' => 1 }, { 'chips' => 1 }]
t.each { |hash| hash['pies'] += 1 if hash.key?('pies') }
# => [{"pies"=>2}, {"burgers"=>1}, {"chips"=>1}]
Hope this helps!
If you know there's only one hash that could take the key 'pies' then you can use find and increase the value it has, like:
array = [{ 'pies' => 1 }, { 'burgers' => 1 }, { 'chips' => 1 }]
pies_hash = array.find { |hash| hash['pies'] }
pies_hash['pies'] += 1
p array
# [{"pies"=>2}, {"burgers"=>1}, {"chips"=>1}]
Enumerable#find will try to find the element that satisfies the block and stops the iteration when it returns true.
You're using the wrong data structure. I recommend using a Hash.
Each item on your menu can only have one count (or sale), that is each item is unique. This can be modelled with a hash with unique keys (the items) and their corresponding values (the counts).
t = {'pies' => 1, 'burgers' => 1, 'chips' => 1}
Then we can access keys and add to the count:
t['pies'] += 1
t #=> t = {'pies' => 2, 'burgers' => 1, 'chips' => 1}

Changing value of an array in Perl

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

Parsing a HoAoHoAoHoAoH in Perl

I am new to Perl and have a little idea about hashes. I have a hash of array of hash of array of hash of array of hash (HoAoHoAoHoAoH) as follows.
%my_hash = (
key00 => 'value00',
key01 => [
{ key10 => 'value10',
key11 => 'value11',
key12 => [
{ key20 => 'value20',
key21 => 'value21',
key22 => [
{ key30 => 'value30',
key31 => [
{ color => 'blue', quantity => 10, boxes => [0,1,3] },
{ color => 'red', quantity => 2, boxes => [2,3] },
{ color => 'green', quantity => 5, boxes => [0] },
],
},
],
},
]
}
]
);
What is the easiest way to access the "color", "quantity" and "boxes"? I also need to do arithmetic operations with the "quantity"s, such as 10+2+5 (quantity0+quantity1+quantity2).
This looks a lot like an XY problem. What are you trying to solve here?
You can access an element of your data structure like this:
print $my_hash{key01}[0]{key12}[0]{key22}[0]{key31}[0]{color},"\n";
You can also iterate the bottom elements with:
foreach my $something ( #{ $my_hash{key01}[0]{key12}[0]{key22}[0]{key31} } ) {
print $something->{'color'};
print $something->{'quantity'}
}
But this doesn't look like a real problem - what are you actually trying to accomplish? I might guess you're trying to parse XML or similar, in which case there's almost certainly a better approach.

Sorting an array of hashes, based on the value of a particular key within each hash in perl

I've done some searching and struggled to find the answer to this one.
Say I have an array of hashes like this..
my #AoH = (
{ 'ip_type' => 1,
'ip_ref' => 0001,
'ip_address' => '192.168.0.1',
'ip_priority' => '100'
},
{ 'ip_type' => 1,
'ip_ref' => 0002,
'ip_address' => '192.168.0.2',
'ip_priority' => '1'
}
);
In context, these are multiple IP addresses, in which I intend to load balance across. The 'ip_priority' value dictates which is first, and subsequently which is second in pirority. primary and backup, basically.
However, I want to order the array elements (aka the hashes) numerically based on the ip priority value within the hash.
So, preferably the above AoH would be...
my #AoH = (
{ 'ip_type' => 1,
'ip_ref' => 0002,
'ip_address' => '192.168.0.2',
'ip_priority' => '1'
},
{ 'ip_type' => 1,
'ip_ref' => 0001,
'ip_address' => '192.168.0.1',
'ip_priority' => '100'
}
);
so $AoH[0]->{ip_priority} now cotains 1 and $AoH[1]->{ip_priority} contains 100.
How can I perform a sort in order to achieve the second example above? im struggling to find a workable example!
You can use:
my #sorted = sort {$a->{ip_priority} <=> $b->{ip_priority}} #AoH;

Perl, How to delete a hash in array of hashes?

I have an Array of Hashes as below.
#students= (
{
'math' => 95,
'phy' => 90,
'che' => 85
},
{
'math' => 50,
'phy' => 70,
'che' => 35
}
);
I want to delete a entire hash based on some conditions, for that i tried with below code but am getting an error saying delete argument is not a HASH or ARRAY element or slice. So please help me, how can i do?
for $i ( 0 .. $#students) {
for $key ( keys %{ $students[$i] } ) {
if ($key eq 'che') {
if ($students->{$key} == 35){
delete (%{$students[$i]});
}
}
}
}
Deleting is well suited for hash keys, but in your case you want to remove array elements so grep filtering could be applied:
#students = grep { $_->{che} != 35 } #students;

Resources