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

I've got this snippet (part of a large script that runs with strict & -w!) and wondered if there was an easier way of getting the number of keys in the hash that satisfy a condition (or two) than looping through the hash and checking each element as I do below.

for $clipnum(0 .. $noofclips-1) { if ($file{$clipnum}{FILETYPE} eq "Menu") { $menucount++; if ($file{$clipnum}{VOICEOVER} eq "No") { $menunvocount++; } else { $menuvocount++; } } if ($file{$clipnum}{FILETYPE} eq "Video") { $videocount++; if ($file{$clipnum}{VOICEOVER} eq "No") { $nvocount++; } else { $vocount++; } } }
Can you do something like the below in one line instead?

$menucount = (all keys in hash that equal) ($file{$clipnum}{FILETYPE} +eq "Menu" && ($file{$clipnum}{VOICEOVER} eq "No")

I know how to find the total number of keys in a hash - $num_keys = scalar keys %file; but not how to make it do that if & only if certain conditions are met.

thanks for your help in advance

dmtelf

Replies are listed 'Best First'.
Re: Keeping a count of matches in a hash that satisfy more than 1 condition
by lhoward (Vicar) on Jul 19, 2000 at 17:35 UTC
    There are shorter solutions line-of-code wise. But all the solutions will still involve iterating through the entire hash of hashes and counting.

    You could use something like this, which is shorter but not necessarily faster.

    foreach (keys %file){ $stats{$file{$_}{FILETYPE}}++; $stats{$file{$_}{FILETYPE}."_".$file{$_}{VOICEOVER}}++; } foreach (keys %stats){ print "$_ => $stats{$_}\n"; }
    You could do each count in a fairly succinct one-liner.
    $stats{$file{$_}{FILETYPE}}++ for keys (%file);
(jjhorner)Keeping a count of matches in a hash that satisfy more than 1 condition
by jjhorner (Hermit) on Jul 19, 2000 at 17:42 UTC

    You could do something like this, but this is still looping:

    foreach my $clipnum (keys %file) { $menucount++ if ($file{$clipnum}{FILETYPE} eq "Menu"); $menunvocount++ if ($file{$clipnum}{FILETYPE} eq "Menu" && $file +{$clipnum}{VOICEOVER} eq "No"); $videocount++ if ($file{$clipnum}{FILETYPE} eq "Video"); $nvocount++ if ($file{$clipnum}{VOICEOVER} eq "No" && $file{$cli +pnum}{FILETYPE} eq "Video"); $vocount = $videocount - $nvocount; $menuvocount = $menucount - $menunvocount; }

    I know this isn't exactly what you asked, but I believe looping is the only way.

    J. J. Horner
    Linux, Perl, Apache, Stronghold, Unix
    jhorner@knoxlug.org http://www.knoxlug.org/
    
Re: Keeping a count of matches in a hash that satisfy more than 1 condition
by eduardo (Curate) on Jul 19, 2000 at 17:39 UTC
    that sounds like a job for... grep! (now this is untested, but i think i got everything right...)
    $menucount = scalar(grep {($file{$_}{FILETYPE} eq "Menu") && ($file{$_}{VOICEOVER} eq "No") } keys %file);
    sound good?

      I believe the asker wants to come out of this with 6 new counts, so you would have to use 4 greps, and a couple of subtractions to get all of the counts needed.

      J. J. Horner
      Linux, Perl, Apache, Stronghold, Unix
      jhorner@knoxlug.org http://www.knoxlug.org/
      
        very true... i didn't really want to type out all of the greps though! :) the question, as always, now becomes... which is faster, the foreach or the grep! anyone up for a benchmarking of the two? (my guess is the foreach will probably be quicker... but, i would be really curious to see what turns out...)
Re: Keeping a count of matches in a hash that satisfy more than 1 condition
by turnstep (Parson) on Jul 19, 2000 at 19:26 UTC

    Here is another way to do it. Fairly short and clean, although I ended up writing a complete script as a proof of concept:

    #!perl use strict; my $numClips=15; my @types = ("Menu", "Audio", "Video", "Snacks"); my @answer = ("Yes", "No", "N/A"); my (%file, %counttype, %yes, %no); ## Generate some random data for our test: for (0..$numClips-1) { $file{$_}{FILETYPE} = @types[rand(@types)]; $file{$_}{VOICEOVER} = @answer[rand(@answer)]; } pop(@types); ## Remove "Snacks" for our test my $types = join('|', @types); ## Or just hardcode the above as: $types = "Menu|Audio" etc... ## The main magic: for my $clipnum (0..$numClips-1) { if ($file{$clipnum}{FILETYPE} =~ /^($types)$/o) { $counttype{$1}++; $file{$clipnum}{VOICEOVER} eq "No" ? $no{$1}++ : $yes{$1}++; } } print "Results:\n"; for $a (sort keys %counttype) { print "$a: $counttype{$a}\n"; print " Yes: $yes{$a}\n"; print " No: $no{$a}\n"; }
Re: Keeping a count of matches in a hash that satisfy more than 1 condition
by barndoor (Pilgrim) on Jul 19, 2000 at 19:32 UTC
    If you stick the count variables in a hash you can reduce the loop code a bit. Not sure if this is any faster or more efficient. You have to define the count structure up front as well.
    my $count = { "Menu" => { "Count" => 0, "Voice" => 0, "NoVoice" => 0}, "Video" => { "Count" => 0, "Voice" => 0, "NoVoice" => 0} }; foreach my $entry (values(%$file)) { $count->{$entry->{FILETYPE}}->{"Count"}++; if($entry->{VOICEOVER} eq "No") { $count->{$entry->{FILETYPE}}->{"NoVoice"}++ } else { $count->{$entry->{FILETYPE}}->{"Voice"}++ } } # Example getting one of the counts out. print $count->{"Menu"}->{"Count"} . "\n";
    When I started this I thought it would come out smaller than this. Thought someone might find it useful or make it a bit smaller.