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

I am trying to match a partial ISBN (the last 5 digits before the checksum) against a hashref keyed by full ISBNs.

Mostly, I'm working with complete 10-digit ISBNs but for the occasional incompletes, I'd like to be able to use an expression as the key with exists. Something like this...
if ( $incomplete_isbn =~ m/^\d{5}$/ ) { my $re_key = qr/.+$incomplete_isbn.+/; if ( exists $hashref_isbn->{$re_key} ) { print "Found! $incomplete_isbn matches $hashref_isbn->{$re_key +}\n"; } }

Replies are listed 'Best First'.
Re: Possible to use an expression as the hash key with exists?
by ikegami (Patriarch) on Sep 18, 2007 at 16:04 UTC

    The underlying structure doesn't permit this to be done without iterating over every key until the condition is matched.

    If there's a possibility of multiple matches,

    if ( $incomplete_isbn =~ m/^\d{5}$/ ) { my $re_key = qr/.+$incomplete_isbn.+/; my @matching_keys = grep /$re_key/, keys %$hashref_isbn; ... }

    But it sounds like you want to check the same digits of the ISBN every time, and that those digits are unique. If so, then your hash is incorrectly keyed. It should be keyed by the partial ISBN. The full ISBN can be included as part of the value.

    my %titles = ( partial_isbn => [ full_isbn, title ], partial_isbn => [ full_isbn, title ], partial_isbn => [ full_isbn, title ], partial_isbn => [ full_isbn, title ], );

    By the way, qr/.+$incomplete_isbn.+/ doesn't seem to match what you specified, and can be simplified to qr/.$incomplete_isbn./.

    By the way, $hashref_isbn is an aweful name. What does the hash contain, a list of titles? Then it should be called "titles" or "titles by isbn", not "isbn".

    By the way, Tie::Hash::Regex does what you say you want, but I don't think it's a good solution to your problem.

    Updated due to accidental post.

      I know what you mean about vague variable names. In this case $hashref_isbn holds everything from our isbn table so for me and colleagues the name does have meaning.

      Thanks for your idea about a hash keyed by partial ISBNs! Clever, clever.

      Here's what I ended up using:
      my @full_isbns = ( defined $hashref_isbn ? keys %{$hashref_isbn} : () +); my %isbn_by_stub = (); foreach my $full_isbn ( @full_isbns ) { my $stub_isbn = substr $full_isbn, 6, 5; $isbn_by_stub{$stub_isbn} = $isbn; } # 4-digit ISBN stub if ( $isbn =~ m/^\d{4}$/ ) { $isbn = 0 . $isbn; } # 5-digit ISBN stub if ( $isbn =~ m/^\d{5}$/ ) { if ( exists $isbn_by_stub{$isbn} ) { print "Found! $isbn matches $isbn_by_stub{$isbn}\n"; } }
Re: Possible to use an expression as the hash key with exists?
by Skeeve (Parson) on Sep 18, 2007 at 16:03 UTC

    It's not possible. I see 2 options:

    1. When creating the %$hashref_isbn, also create an %incomplete_isbn which references an array containing the complete keys (or at least the parts completing the isbn)
    2. iterate through al complete isbns you have, searching for matches

    s$$([},&%#}/&/]+}%&{})*;#$&&s&&$^X.($'^"%]=\&(|?*{%
    +.+=%;.#_}\&"^"-+%*).}%:##%}={~=~:.")&e&&s""`$''`"e
Re: Possible to use an expression as the hash key with exists?
by dwm042 (Priest) on Sep 18, 2007 at 17:08 UTC
    The grep command can do the kinds of things you're looking to do.

    #!/usr/bin/perl use warnings; use strict; my @isbn = (); my %hash = (); for ( 0 .. 100) { $isbn[$_] = &gen_isbn_13; } push @isbn, "9780523456789"; push @isbn, "9780523412345"; while(my $pattern = <DATA>) { chomp($pattern); print "Testing pattern = $pattern\n"; $hash{$pattern} = &isbn_exists( $pattern, \@isbn ); print "Matches found! ISBN = $_ \n" for ( @{$hash{$pattern}} ); } sub isbn_exists { my $pattern = shift; my $isbn_arrayref = shift; my @answer = grep { /\Q$pattern\E$/ } @{ $isbn_arrayref }; return \@answer; } sub gen_isbn_13 { my $num = "978"; for ( 1 .. 10 ) { my $digit = int(rand(10)); $num .= $digit; } return $num } __DATA__ 12345 97833 97810 97805
    One run of the program looks like:

    C:\Code>perl isbncheck.pl Testing pattern = 12345 Matches found! ISBN = 9780523412345 Testing pattern = 97833 Testing pattern = 97810 Testing pattern = 97805
    Update: More exact match to original pattern