dirtdog has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,

I have an array of hashes with sports teams and I define what should be part of the list of teams as TEAMS, and then I use __DATA__ to input the list of ACTUAL_TEAMS. My goal is to then check to see what team(s) provided as ACTUAL_TEAMS are missing from TEAMS. In my example it should be only the GIANTS that are missing, but for some reason it's not working.

#!/usr/bin/env perl use strict; my %ACTUAL_TEAMS; my %TEAMS = ( "NFL" => [ 'JETS', 'PATRIOTS', 'GIANTS', ], "MLB" => [ 'YANKEES', 'METS', 'CARDINALS', ], "NBA" => [ 'SIXERS', 'CELTICS','LAKERS', ], ); while (<DATA>) { chomp; next if /^\s*$/ || /^\#/; my $aref = [split /,/, $_]; push( @{$ACTUAL_TEAMS{$aref->[0]}}, $aref->[1]); } foreach my $group (keys %TEAMS) { #print "The members of $group are\n"; foreach (@{$TEAMS{$group}}) { # print "\t$_\n"; print "MISSING:\tSPORT:$group\tTEAM:$_\n" unless (my @type = grep /\b$ +_\b/, @{$ACTUAL_TEAMS{$group}}); } } __DATA__ NFL,JETS NFL,PATRIOTS MLB,YANKEES MLB,CARDINALS MLB,METS NBA,SIXERS NBA,CELTICS NBA,LAKERS

Any help would be greatly appreciated. Thanks

Replies are listed 'Best First'.
Re: Tyring to grep array of hashes
by kennethk (Abbot) on Feb 07, 2017 at 15:55 UTC
    my @type = grep /\b$_\b/, @{$ACTUAL_TEAMS{$group}}
    This will always be true, because in the grep, $_ equals the active element -- you've written a tautology. Perhaps you mean:
    #!/usr/bin/env perl use strict; my %ACTUAL_TEAMS; my %TEAMS = ( "NFL" => [ 'JETS', 'PATRIOTS', 'GIANTS', ], "MLB" => [ 'YANKEES', 'METS', 'CARDINALS', ], "NBA" => [ 'SIXERS', 'CELTICS','LAKERS', ], ); while (<DATA>) { chomp; next if /^\s*$/ || /^\#/; my $aref = [split /,/, $_]; push( @{$ACTUAL_TEAMS{$aref->[0]}}, $aref->[1]); } foreach my $group (keys %TEAMS) { #print "The members of $group are\n"; foreach my $team (@{$TEAMS{$group}}) { # print "\t$_\n"; print "MISSING:\tSPORT:$group\tTEAM:$team\n" unless (my @type = grep / +\b$team\b/, @{$ACTUAL_TEAMS{$group}}); } } __DATA__ NFL,JETS NFL,PATRIOTS MLB,YANKEES MLB,CARDINALS MLB,METS NBA,SIXERS NBA,CELTICS NBA,LAKERS

    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: Tyring to grep array of hashes
by 1nickt (Canon) on Feb 07, 2017 at 16:20 UTC

    Hi dirtdog,

    I can't tell from your posts whether you are trying to solve a problem or complete a homework assignment, but if you are free to, I would dump your arrays and use hashes, which are much better suited to this type of task.

    For example:

    #!/usr/bin/env perl use strict; use warnings; my %teams = ( NFL => { JETS => 1, PATRIOTS => 1, GIANTS => 1 }, MLB => { YANKEES => 1, METS => 1, CARDINALS => 1 }, NBA => { SIXERS => 1, CELTICS => 1, LAKERS => 1 }, ); my %actual_teams; while ( <DATA> ) { chomp; my ( $league, $team ) = split /,/; $actual_teams{ $league }->{ $team } = 1; } foreach my $league ( keys %teams ) { foreach my $team ( keys %{ $teams{ $league } } ) { if ( not $actual_teams{ $league } or not $actual_teams{ $leagu +e }->{ $team } ) { print "MISSING:\tLEAGUE: $league\tTEAM: $team\n" } } } __DATA__ NFL,JETS NFL,PATRIOTS MLB,YANKEES MLB,CARDINALS MLB,METS NBA,SIXERS NBA,CELTICS NBA,LAKERS
    Hope this helps!


    The way forward always starts with a minimal test.

      Hi lnickt

      I like the hash idea, but this example doesn't seem to be working. it should result in GIANTS being the only missing team.

        it should result in GIANTS being the only missing team.

        As indeed they are:

        $ ./1181308.pl MISSING: LEAGUE: NFL TEAM: GIANTS $

        Perhaps you have downloaded it incorrectly?

        Make sure to copy and paste the source using the download link (which displays as [d/l] to the right of the post at the bottom), to avoid any browser/html oddities or typos. The code runs as follows for me:

        $ perl 1181302.pl MISSING: LEAGUE: NFL TEAM: GIANTS


        The way forward always starts with a minimal test.

        My apologies ...it is working

        Thanks!

Re: Tyring to grep array of hashes (updated)
by haukex (Archbishop) on Feb 07, 2017 at 16:10 UTC

    Hi dirtdog,

    In addition to kennethk's solution of your problem, a few points on the grep:

    • You don't seem to need the variable @type, or if you do, I'd recommend breaking it out into a separate statement for readability. That is, consider one of these alternatives:
      1. print "..." unless grep /\b$team\b/, ...;
      2. my @type = grep /\b$team\b/, ...; print "..." unless @type;
      3. my $type = grep /\b$team\b/, ...; print "..." unless $type; (in scalar context, grep returns a count of matched items)
    • Since you're just trying to see if any of the elements of the array match, and you're not interested in all matches, consider using first or any from List::Util. Or, if you're just interested in exact matches, build a hash from the array where the keys are the elements of the array, and then use hash lookups.
    • Consider using /\b\Q$team\E\b/, as otherwise any characters in $team which happen to be regex metacharacters (such as .) will keep their special meaning. See also Mind the meta!
    • The regex /\b$team\b/ will match even if may match in some cases when $team is only a substring of the string being searched. If you want exact matches, you can use grep {$_ eq $team} ... (but in that case, a hash lookup would probably be better).

    Updated: Updated text of last bullet point slightly, as per Athanasius's reply.

    Hope this helps,
    -- Hauke D

      Hello haukex,

      This is mostly good advice, but this part:

      • The regex /\b$team\b/ will match even if $team is only a substring of the string being searched.

      looks wrong to me: \b in a regex matches a word boundary, so substrings will not match:

      2:24 >perl -wE "for (qw[cat catastrophe]) { say qq[$_: matches] if /c +at/; }" cat: matches catastrophe: matches 2:26 >perl -wE "for (qw[cat catastrophe]) { say qq[$_: matches] if /\ +bcat\b/; }" cat: matches 2:26 >

      See “Assertions” under perlre#Regular-Expressions. But I agree that eq is better than a regex in this situtation, and a hash lookup would be preferable to using grep.

      Update: Anonymous Monk, below, makes an excellent point: some substrings will still match. A word boundary is defined as:

      a spot between two characters that has a \w on one side of it and a \W on the other side of it (in either order), counting the imaginary characters off the beginning and end of the string as matching a \W.

      — and a hyphen is considered a “non-word” (i.e., it matches /\W/).

      Cheers,

      Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        Hi Athanasius,

        You're absolutely right of course. My intention was to warn that, if only exact matches are desired, "ABC DEF GHI"=~/\bDEF\b/ ("DEF" being a "substring" of "ABC DEF GHI") would give a false positive. But by omitting a discussion of \b, my node became inaccurate, so thank you for the reply :-)

        Thanks,
        -- Hauke D

        for (qw[cat catastrophe cat-ass-trophy]) { say qq[$_: matches] if /\bcat\b/ }