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

Hello I have a text file (newestm.txt) and a hash (%whop). This script is to print out the newest members. I have a whos online script that prints who is online also. I want to print out the newest members and if that member is new and online then it prints out the member name like this, VegeTa343* and if its not on but just one of the newest members then itll just be, VegeTa343.

I decided to read the newestm.txt into a array, and then use a foreach statement to see if that new member is in the DBM file that contains members online.

#open newest open(NEW,"<$path/newestm.txt"); @newest_members = <NEW>; close(NEW); #open whosonline use DB_File; tie %whop, "DB_File", "$path/whosonline" or die "Can't open FILENAME: +$!\n"; foreach $newm (@newest_members) { #############THIS IS THE PART I DON'T KNOW ;'( if blahblha (values %whop) #im figuring its going to need a if s +tatement print <<EOF; $newm<b>*</b> - EOF } else { print <<EOF; $newm - EOF } } untie %whop; dbmclose %whop;


Anthony

Replies are listed 'Best First'.
Re: Looking up a hash by value
by Tyke (Pilgrim) on Feb 23, 2001 at 13:45 UTC
    If you looking up a lot of hashes by value then you might want to create a lookup hash like
    my %lookup;
    $lookup{$_}++ for values %whop;
    

    Then your test can become something like

    print for map{exists $lookup{$_} ? "$_*\n" : "$_\n"} @newest_members;
    
    Warning: untested code but it should give you the idea
Re: Looking up a hash by value
by a (Friar) on Feb 23, 2001 at 11:16 UTC
    Besides (and you've been at this a while so just do it) -w/use strict and
    or die "can't open $path/newestm.txt: $!";
    I'm guessing your %whop hash as member names as values and ... well, lets say IP addresses as keys. One way would be:
    foreach $newm (@newest_members) { my $online; foreach my $whos_on ( values %whop ) { $online++ if $whos_on eq $newm; } my $star = '<b>*</b>' if $online; print <<EOF; $newm $star - EOF } # foreach newm
    yes, wasteful and a map would probably be cool or $who_list = join '|', values %whop; and then if ($newm =~ /$who_list/ but you get the idea.

    a

      Rather than iterating through %whop, you can use reverse to swap the keys and values of a hash, then use exists:

      my %whop_value = reverse %whop; foreach my $newm (@newest_members) { $newm .= '<b>*</b>' if exists $whop_value{$newm}; $newm .= ' - '; print $newm; }

      Using reverse on hashes is described in perlfunc:reverse

(ichimunki) re: Looking up a hash by value
by ichimunki (Priest) on Feb 23, 2001 at 17:23 UTC
    The following assumes that the key, not the value of %whop holds the names of the online users, which is what I got from reading the code sample.

    First, lose the heredoc for simple concatenation. Heredocs are neat, but they completely destroy readability. This will do:
    print "$newm<b>*</b> - ";
    You could also build this hash during the file read just as easily and save yourself an extra loop.

    Second, I suggest you create a quick hash from your text file and then look through %whop to do your work:
    my %new_member; foreach (@newest_member) { $new_member{$_} = ''; } then for your loop (which as it looks now has the wrong outer loop goi +ng): foreach (values %whop) { if (exists( $new_member{$_} ) { print "$_<b>*</b> - "; } else { print "$_ - "; } }
Re: Looking up a hash by value
by merlyn (Sage) on Feb 23, 2001 at 21:19 UTC
    If you're gonna do this once, or with constantly changing values, a linear search is the best you can get:
    my $seen = 0; while (my ($key, $value) = each %whop) { $seen = 1, last if $value eq $newm; } if ($seen) { ... }
    Otherwise, cache the values %whop as hash keys, and do your lookups there.

    -- Randal L. Schwartz, Perl hacker

      Otherwise, cache the values %whop as hash keys, and do your lookups there.

      Using reverse is faster than values (unless I've misunderstood how you'd do this), especially for larger hashes (I'm using Perl 5.005_03 on FreeBSD 4.2):

      use Benchmark qw(timethese); use constant VALUE => 5000; my %hash = ( 1..VALUE); timethese(1_000_000 / VALUE, { 'reverse' => sub { my %values = reverse %hash; }, 'map_values' => sub { my %values = map {$_ => undef} values %hash; } });

      For VALUE => 5000:
      Benchmark: timing 200 iterations of map_values, reverse...
      map_values: 10 wallclock secs ( 9.40 usr + 0.00 sys = 9.40 CPU)
      reverse: 4 wallclock secs ( 3.51 usr + 0.01 sys = 3.52 CPU)

      For VALUE => 50:
      Benchmark: timing 20000 iterations of map_values, reverse...
      map_values: 3 wallclock secs ( 2.95 usr + 0.00 sys = 2.95 CPU)
      reverse: 3 wallclock secs ( 2.55 usr + 0.01 sys = 2.55 CPU)

        There is a problem with the common way we are making a "lookup by-value hash" to find the corresponding keys in the source hash:

        What if two keys in a hash have the same value? When reversing the hash, or creating the lookup hash, the last identical value will clobber those before it. Here's some code to show you what I mean:

        #!/usr/bin/perl -w use strict; use Data::Dumper qw(Dumper); my %hash = ( a => 1, b => 1, c => 1, d => 2, e => 2, f => 2, ); my %by_value = reverse %hash; print Dumper(\%by_value);

        will print out something like:

        $VAR1 = { '1' => 'a', '2' => 'e' };

        This isn't what we want, there was a loss of information, during the copy to %by_value, the keys "a" and "e" were the last to have the values of 1, and 2 respectively. Since they were being assigned to a new hash, the last to get copied wins. Even worse, the order that the reverse is done in is not garaunteed to be the same, so you could get unpredictible results on other computers or possibly even from different versions of perl.

        IMHO, a better way to do it would be to create a data structure that allows the lookups by value and preserves the original matching keys. Here is one possible way to do it:

        my %hash = ( a => 1, b => 1, c => 1, d => 2, e => 2, f => 2, ); my %by_value; while(my($key, $value) = each %hash) { push @{ $by_value{$value} }, $key; } print Dumper(\%by_value);

        which will print:

        $VAR1 = { '1' => [ 'a', 'b', 'c' ], '2' => [ 'e', 'f', 'd' ] };

        This above data structure correctly represents the relationship between a key and a value in a hash. That is, it allows any given value to have one or more keys in a hash. Accessing this structure is simple, if you want to see all the matching keys that have a value of "1", you can access the %by_value hash, like this:

        print join "\n", @{ $by_value{1} };