Search code examples
perlrefactoringperl-data-structuresperl-hash

Shorten code: merge arrays from hashes


I have a list of hashes and some of the hashes contain a key which provides an array itself.

my @cars = (
   { # empty car
      name => "BMW",
   },
   { # car with passengers
      name => "Mercedes",
      passengers => [qw(Paul Willy)],
   },
   ...
)

It's pretty much like above but of course not with the stupid cars example:-)

Now I need to get a list of all "passengers" from all hashes including ones that do not even offer a passengers array.

In a 2nd step I need to retrieve unique entries from the list (actually the passengers are Perl object refs and I need every object once in the list)

At the moment I do:

my (@all, @passengers, %seen);
for(@cars) {
    push @all, @{$_->{passengers}} if $_->{passengers};
}

@passengers = grep { ! $seen{$_} ++ } @all;

I would like to get rid of @all and throw the list of all passengers directly into grep.

Any suggestions?


Solution

  • Here's another variation. It uses List::MoreUtils::uniq. The %seen stuff is good to know, but unnecessary these days.

    use List::MoreUtils qw<uniq>;
    
    my @passengers 
        = sort uniq map { @$_ } grep { defined } map { $_->{passengers} } @cars
        ;
    

    Of course, using my idiom of list_if, I would just do this:

     my @passengers = sort uniq map { list_if( $_->{passengers} ) } @cars;
    

    Where list_if is defined as:

    sub list_if {
        use Params::Util qw<_ARRAY _HASH>;
    
        return unless my $cond = shift;
        return unless my $ref
            = @_ == 0 ? $cond
            : @_ == 1 ? $_[0]
            :          \@_
            ;
        return !ref( $ref )   ? $ref
             : _ARRAY( $ref ) ? @$ref
             : _HASH( $ref )  ? %$ref
             :                  ()
             ;              
    }
    

    It's been a useful idiom for cutting down on long-hand approaches to deciding whether to "stream" array and hash refs or not.