1

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?

3
  • List::Util::max doesn't return index but value itself. Commented Mar 17, 2015 at 10:27
  • Also, List::Util::max works on list of numbers, not bigger data structures. How do you compare your values? Are "Trousers" > "Socks" (because 40 > 5) or other way round (because 12 > 2). Commented Mar 17, 2015 at 10:47
  • It seems to me everyone assumes there must be a unique item with the highest price. While the highest price itself is unique, there is no a priori reason the number of items with the highest price must be one. Commented Mar 17, 2015 at 12:06

5 Answers 5

3

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];
Sign up to request clarification or add additional context in comments.

1 Comment

I had not seen your answer when I thought everyone else was assuming there could only be one item with the highest price.
0

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;

Comments

0

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

1 Comment

reduce is nice, but you are assuming there can only be one item with the highest price.
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.