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

This code snippet creates a hash from a CSV phonebook list (number,name). Sample data is at the end of the script. 4 tests are then run on this hash to see if the data is extracted correctly.

The first two tests both return "Unknown Caller". I think the hash has been built and populated correctly as the last two tests work. The first test should return "Tom". The second test should return 'Unknown Caller'.

The problem appears to be in &MatchPhoneBook which returns either a user's name if their number is in the hash or 'Unknown caller'. It currently always returns 'Unknown Caller', no matter what number is fed to it.

Can some Monk please explain why the first two tests do not work as expected? What am I doing wrong?

thanks

dmtelf

use strict; my (%PhoneBook,$TheCaller); my ($ValidTest,$InvalidTest); my ($name,$NumToFind); # Read in CSV phone book &ReadPhoneBookFile; # Run two parameter tests $ValidTest = "8954"; $InvalidTest = "9999"; print "Parameter test 1 - Number to find is: $ValidTest\n"; $TheCaller = &MatchPhoneNumber($ValidTest); print "The caller was: $TheCaller\n"; print "\nParameter test 2 - Number to find is: $InvalidTest\n"; $TheCaller = &MatchPhoneNumber($InvalidTest); print "The caller was: $TheCaller\n"; # Run two direct tests. # First one works, second one doesn't. print "\nDirect test 1 (8594) - ".$PhoneBook{8594}; # WORKS! $NumToFind = "8594"; print "Direct test 2 (8594) - ".$PhoneBook{$NumToFind}; # DOES NOT +WORK! exit; sub ReadPhoneBookFile ### ### Reads in a CSV separated phone book, splits, stuffs details into +a hash ### ### In real-world script, data is read from an external file. { my ($Name,$Number,@Details); foreach (<DATA>) { @Details = split(/,/,$_); $Number = $Details[0]; $Name = $Details[1]; $PhoneBook{$Number} = $Name; } } sub MatchPhoneNumber ### ### Attempts to match the phone number with a name from the phonebook ### Either returns the caller name if number in hash or "Unknown calle +r". ### { my $NumToFind = shift; my $TheName; $TheName = defined $PhoneBook{$NumToFind} ? $PhoneBook{$NumToFind} + : 'Unknown Caller'; return $TheName; } __DATA__ 8594,Tom 9000,Dick 1234,Harry

Replies are listed 'Best First'.
Re: Data extraction from hash problem
by maverick (Curate) on Aug 07, 2000 at 00:18 UTC
    you have a transposition error.

    8594 in the data
    8954 in $ValidTest

    Both of the 'direct' tests work for me, I don't see anything wrong with them.

    /\/\averick

Re: Data extraction from hash problem
by tilly (Archbishop) on Aug 07, 2000 at 00:23 UTC
    My first piece of advice is "perldoc Test" - a good test harness is an invented wheel.

    OTOH that does not solve the problem at hand. And that problem is that the number in the hash has a space in it. The number you are looking up does not.

    A stupid way to dump complex data structures is to insert something like this at an appropriate place in your script:

    use Data::Dumper; print Dumper(\%PhoneBook);
    In this case that will give:
    $VAR1 = { ' 9000' => 'Dick ', ' 1234' => 'Harry ', ' 8594' => 'Tom ' };
    and you see the space in the number and the return in the name that you probably didn't want.

    BTW it is good practice to declare hashes you plan to use as global data with "use vars".

    As always, /tell tilly if you have questions about this.

RE: Data extraction from hash problem
by Russ (Deacon) on Aug 07, 2000 at 01:05 UTC
    Well, I was out in space, here. It's not a scoping problem at all, as others have mentioned.

    Oh, well.

    ReadPhoneBookFile() cannot "see" %PhoneBook, because %PhoneBook is lexical.

    Okay, here is the code after I made a few changes to it.

    use strict; use vars qw/%PhoneBook/; # Read in CSV phone book ReadPhoneBookFile(); # Run two parameter tests my $ValidTest = "8594"; my $InvalidTest = "9999"; print "Parameter test 1 - Number to find is: $ValidTest\n"; print 'The caller was: ', MatchPhoneNumber($ValidTest), "\n"; print "\nParameter test 2 - Number to find is: $InvalidTest\n"; print 'The caller was: ', MatchPhoneNumber($InvalidTest), "\n"; exit; sub ReadPhoneBookFile ### ### Reads in a CSV separated phone book, splits, stuffs details into +a hash ### ### In real-world script, data is read from an external file. { foreach (<DATA>) { my @Details = split(/,/,$_); $PhoneBook{$Details[0]} = $Details[1]; } } sub MatchPhoneNumber ### ### Attempts to match the phone number with a name from the phonebook ### Either returns the caller name if number in hash or "Unknown calle +r". ### { my $NumToFind = shift; return defined $PhoneBook{$NumToFind} ? $PhoneBook{$NumToFind} : 'Unknown Caller'; } __DATA__ 8594,Tom 9000,Dick 1234,Harry
    A few notes:
    • You had 8954 in some places, and 8594 in others
    • As noted above, %PhoneBook was lexical, therefore not in scope when ReadPhoneBookFile() and MatchPhoneNumber() were called.
      • We use use vars to put %PhoneBook in the symbol table for this scope and keep use strict happy.
      • We could also just pass %PhoneBook to the functions. Many would argue this is a better way...
    • I removed many of your temporary variables. This is mainly just an issue of performance and style. No offense...
    So, it was just a scoping issue...

    Russ
    Brainbench 'Most Valuable Professional' for Perl

RE: Data extraction from hash problem
by Russ (Deacon) on Aug 07, 2000 at 00:14 UTC
    Good grief! Wrong and multiple posts! When it rains it pours... :-)

    /me slinks away in shame

    ReadPhoneBookFile() cannot "see" %PhoneBook, because %PhoneBook is lexical.

    I see you are still lurking around the monastery, so I will post this, and followup with more detail shortly.

    Russ
    Brainbench 'Most Valuable Professional' for Perl

      Beg to differ, %PhoneBook is lexical, but it's declared lexically for the scope of the entire program (at the top of the page).

      Yes, there are betters ways to do it! dmtelf, I would definitely try something like this:

      our %PhoneBook = &ReadPhoneBookFile(); sub ReadPhoneBookFile { my %TempBook; ... some stuff ... return %TempBook; }

      This would have been much more clearer, and our* is the best keyword for global variables like that (a variable used between subroutines without explicitly being passed). HTH :-)

      Alakaboo

      * I should clarify this. use vars has been somewhat depreciated in favor of our since Perl5.6.0. Since it will no doubt be used prevalently henceforth, and in Perl6, I make it a general rule to use it. If you're running an older version of Perl, you'll need to do:

      use vars qw(%PhoneBook); %PhoneBook = &ReadPhoneBookFile();

      It's just cleaner to use our! (thanks tilly, ya beat me to it!)

        Unfortunately our only works in Perl 5.6. Perl 5.6.0 is most definitely not ready for production. Try this code to see why not:
        perl -e 'my $x = 10; $x = "2" . $x; print $x + 0'
        If you don't have 5.6.0 installed the problem is that when you altered $x with a string operation, the number was not properly invalidated, leading to data corruption.

        But with 5.6.1 (which has a *lot* of bug fixes) then I heartily agree that our is better.

        EDIT
        I was asked where to find 5.6.1. Well, um, you can't. Check back next month. As I understand it 5.7.0 will come out first (next development series), and then 5.6.1 will follow that. But there are a lot of good bug fixes in current bleeding edge Perl's. (I certainly have them on my home machine.)

        OK, so where do you get that? Um, I don't know. :-( I know where an ftp server used to be with a fairly recent snapshot. It isn't there any more and I don't follow this stuff closely. I can track down instructions for the perforce snapshot, but because of firewalls I cannot test that advice. And I don't even know if that advice will still work given the recent handing over of development responsibilities to Jarko Hietaniemi. (Which is presumably why the ftp server moved.)

        Visit back here and I will update this when I find a real answer. (If anyone knows then /tell me or post here. Thanks.)

        EDIT 2
        Aren't you glad you visited back? Apparently ActiveState is merely having some technical problems. But there is a private snapshot from the current maintainer available. This is on its way to becoming 5.7.0. Basically avoid anything that says it is experimental. (Uh, you should do that anyways.:-) Believe it or not, I would be more inclined to trust this snapshot than the current 5.6.0. YMMV.

        EDIT 3
        Sarathy claims that their ftp server has been being accessed since Aug 1. So try that and hopefully YMMV from mine. (That machine has more snapshots, and the previous link is only going to work temporarily.) I have no idea what the issue is, it could be local to my home. :-(

        EDIT 4
        Someone else reproduced the problem. There appears to be an active/passive firewall barrier. Set your ftp client accordingly and the Activestate link should work.

        EDIT 5
        Sarathy has announced that there will be at least temporary public http access at http://public.ActiveState.com/gsar/APC/. Plus the firewall issues are being looked at.

Re: Data extraction from hash problem
by markguy (Scribe) on Aug 07, 2000 at 21:21 UTC
    I think your question has been answered pretty thoroughly... I'd like to point out that it's a phenomenally good idea to ensure that there is only one point of entry for any bit of information you need to keep track of in a program...

    Setting up the $ValidTest variable is a good idea... but if you'd used it throughout the script, instead of the multiple instances of '8594', you might have spotted one of the issues much sooner. Or simply not had it.

    I don't mean to be critical, just helpful... I found that, particularly for long chunks of code, my life was made much simpler when I went on a 'multiple-point-of-data-entry jihad'. I believe there is a lot of good, solid research on this.... I just can't point you to it at the moment :) Pragmatic Programmer has a section or four discussing this, now that I think about it. Good book to read straight through too.

    markguy