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

Hi,

I've just started using hash tables and could use some help with something. I need to whip through two hash tables and provide the following kind of summary:

1) elements common between two tables
2) elements unique to table #1
3) elements unique to table #2

Is there any way to do this with one pass through both hash tables, or do I pretty much have to pass through each hash table once?

Here's what I'm doing:-
%astpd = (); %astslack = (); $ln = 0; while ($in1s[$ln] ne "") { chop ($in1s[$ln]); @in1saa = split(/\s+/,$in1s[$ln]); $aststartend = join (" ",$in1saa[0],$in1saa[1]); $astpd{$aststartend} = $in1saa[2]; $astslack{$aststartend} = $in1saa[3]; undef (@in1saa); $ln++; } %ptpd = (); %ptslack = (); $ln = 0; while ($in2s[$ln] ne "") { chop ($in2s[$ln]); @in2saa = split(/\s+/,$in2s[$ln]); $ptstartend = join (" ",$in2saa[0],$in2saa[1]); $ptpd{$ptstartend} = $in2saa[2]; $ptslack{$ptstartend} = $in2saa[3]; undef (@in2saa); $ln++; }
Any suggestions on the *fastest* way to meet my 3 criteria above? (The keys to match are $aststartend and $ptstartend. Once a match is made, write $astpd, $ptpd, $astslack, and $ptslack to a file...)

Many thanks in advance...

update (broquaint): added <code> tags

Replies are listed 'Best First'.
Re: Hash table question
by hardburn (Abbot) on Jun 24, 2003 at 16:24 UTC

    Assume %hash1 and %hash2 are already defined with the data you need:

    elements common between two tables

    my @common = grep { $_ if exists $hash2{$_} } keys %hash1;

    elements unique to table #1

    my @unique1 = grep { $_ unless exists $hash2{$_} } keys %hash1;

    elements unique to table #2

    my @unique2 = grep { $_ unless exists $hash1{$_} } keys %hash2;

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated

      Uh, not quite. You'll be throwing away any false key ("", "0").

      You want:

      my @common = grep exists $hash2{$_}, keys %hash1; my @unique1 = grep !exists $hash2{$_}, keys %hash1; my @unique2 = grep !exists $hash1{$_}, keys %hash2;
      But if you really want all three at once:
      my %t; $t{$_} .= "1" for keys %hash1; $t{$_} .= "2" for keys %hash2;
      And then each element of %t will be "1" for 1 only, "2" for 2 only, or "12" for both.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

      This is indeed proper*, and is quite efficient. Without needlessly and prematurely optimizing, for the first case, one might consider checking (scalar keys %hash1) <=> (scalar keys %hash2) and perform the constraining loop on the smaller hash. This would only have a significant benefit if the discrepancy in hash sizes is significant.

      * (Except the wreck if it finds the false keys of '' and '0', as merlyn mentioned.)

      Update: Separately, I agree that grep also works as a perl-idiom alternative to foreach, but that shouldn't change the actual effectiveness to my knowledge.

      --
      [ e d @ h a l l e y . c c ]