Search code examples
perlsumhashmap

Add the sum of array values to hash (map / grep)


I've been trying to figure out this one-liner, and can't seem to get it to work.

Say I have this reference to an array of hashrefs:

my $payments = [
   {
      'receipt_id'     => '100',
      'payment_id'     => '1',
      'payment_amount' => 20,
   },
   {
      'receipt_id'     => '100',
      'payment_id'     => '1',
      'payment_amount' => 30,
   },
   {
      'receipt_id'     => '100',
      'payment_id'     => '2',
      'payment_amount' => 40,
   },
   {
      'receipt_id'     => '200',
      'payment_id'     => '1',
      'payment_amount' => 20,
   },
];

What I'd like to do is create a hash where the key is the payment_id, and the value is the sum of all payment_amounts for a particular receipt_id.

I can do:

my %sum_payments = map  { $_->{payment_id} => $_->{payment_amount} }
                   grep { $_->{receipt_id} eq '100' }
                   @$payments;

And get:

1 => 30,
2 => 40,

But when I try:

my %sum_payments = map  { $_->{payment_id} => (sum map { $_->{payment_amount} }) }
                   grep { $_->{receipt_id} eq '100' }
                   @$payments;

It throws errors.

How can I write a one-liner using map / grep to get:

1 => 50,
2 => 40,

Also, I know there are many other ways to achieve the same thing. I'm interested to know if this is possible using a single statement with map / grep.


Solution

  • You just need something to accumulate into - such as assigning directly into the destination hash:

    my %sum_payments;
    
    map { $sum_payments{$_->{payment_id}} += $_->{payment_amount} }
    grep { $_->{receipt_id} eq '100' }
    @$payments;
    

    This is similar to @toolic's answer but, as that says, less readable (and I think less inefficient on large input due to grep building an intermediate list), and some people don't like map used like this.