2

First, this is a homework assignment. I am having a tough time with regex, and I'm stuck.

This is the code I have so far, where I have the user designate a filename, and if it exists, populates a hash of the names as keys, and the phone numbers as the values.

#!/usr/bin/perl

use strict;

print "\nEnter Filename: ";
my $file = <STDIN>;
chomp $file;

if(!open(my $fileName, "<", "$file"))
{
    print "Sorry, that file doesn't exist!", "\n";
}
else
{
    my %phoneNums;
    while (my $line=<$fileName>) 
    {
        chomp($line);
        (my $name,my $number) = split /:/, $line;
        $phoneNums{$name} = $number;
    }

    print "Read in the file!", "\n\n";

    print "Enter search: ";
    my $input = <STDIN>;
    chomp $input;

    #HERE IS WHERE I'M LOST
}

print "\n";

This is the part I am stuck on:

Allow the user to enter a search string. Look for matches using the same style as the phone. Any individual character in the search string can match any other character from the key, meaning a ‘2’ in the search string can match a ‘2’, ‘A’, ‘B’, or ‘C’ in the contact list. Matches can occur in the contact name or the phone number. For a match to occur, each character in the search string must appear, in order, in the contact info, but not necessarily next to each other. For example, a search string of “86” (essentially the same as a search string of “TM” or “NU”) would match “TOM” but not “MOTHER”. Characters on each phone keys: 0, 1, 2ABC, 3DEF, 4GHI, 5JKL, 6MNO, 7PQRS, 8TUV, 9WXYZ

I just am stuck on how exactly to make all those character classes, and any help at all is much appreciated.

G. Cito
  • 6,210
  • 3
  • 29
  • 42
user2951099
  • 41
  • 1
  • 4
  • What you want to do that is not clear. Showing input values means what you have in your hash or in file and how do you want get as output, would be more helpful for you to get answer and for us to give right solution. – serenesat Nov 02 '15 at 05:29

2 Answers2

2

The way to tackle this is by writing a function that reduces your 'things' to their common components. The best way to do this IMO is use a hash:

my %num_to_letter = (
    0 => [],
    1 => [],
    2 => [ "A", "B", "C" ],
    3 => [ "D", "E", "F" ],
    4 => [ "G", "H", "I" ],
    5 => [ "J", "K", "L" ],
    ## etc.

);

my %letter_to_num;
foreach my $key ( keys %num_to_letter ) {
    foreach my $element ( @{$num_to_letter{$key}} ) { 
        $letter_to_num{lc($element)} = lc($key); 
    }
}
print Dumper \%letter_to_num;

This creates a map of which letters or numbers map to their original - a bit like this:

$VAR1 = {
          'b' => '2',
          'g' => '4',
          'e' => '3',
          'i' => '4',
          'a' => '2',
          'j' => '5',
...

Note - you can do this by hand, but I prefer to generate from the top map, because I think it looks neater. Note - we use lc to lower case everything, so this becomes case insensitive. It's probably worth looking at fc - which is a similar tool but handles international characters. (Not relevant in this example though)

You then 'reduce' both search and 'target' to their common values:

sub normalise {
    my ( $input ) = @_;

    #join with no delimiter. 
    return join ( '', 
             #look up $_ (each letter) in $letter_to_num
             #if not present, use // operator to return original value. 
             #this means we get to turn numbers into letters,
             #but leave things that are already numbers untouched. 
             map { $letter_to_num{lc($_)} // $_ } 
                  #split the input line into characters. 
                  split ( //, $input ) 
            );
}

print normalise ("DAD"),"\n";   ## 323

And then compare one against the other:

my $search            = "DAD";
my $normalised_search = normalise($search);
print "Searching for: \"$normalised_search\"\n";

my $number_to_match = '00533932388';
my $string_to_match = "daddyo";

print "Matches number\n"
    if normalise($number_to_match) =~ m/$normalised_search/;
print "Matches string\n"
    if normalise($string_to_match) =~ m/$normalised_search/;
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • ++ simple and real perl - my answer is a bit too clever. To improve my response I started to think about an easy way to determine which of the three or four letters associated with a keypad number was being selected. I did not come up with anything right away ... :-) – G. Cito Nov 02 '15 at 17:44
1

Here's an almost procedural approach that cheats a bit by using Hash::MultiValue:

use Hash::MultiValue; # makes reversing and flattening easier

# build a hash from the phone_keypad array or do it manually!
my @phone_keypad = qw(0 1 2ABC 3DEF 4GHI 5JKL 6MNO 7PQRS 8TUV 9WXYZ);
my %num2let =  map { /(\d{1})(\w{3,4})/; 
               if ($2) { $1 => [ split('',$2) ] } else { 0 => [] , 1 => [] } 
               } @phone_keypad ; 

# Invert the hash using Hash::MultiValue
my $num2let_mv = Hash::MultiValue->from_mixed(\%num2let);
my %let2num = reverse $num2let_mv->flatten ;

# TOM in numbers - 866 in letters
my $letters = "TOM" ;
print join '', $let2num{$_} // $_ for (split('', $letters)), "\n"; 
my $phone_input = "866" ;
print join '', @{$num2let{$_}}," " for (split('', $phone_input)) , "\n";

Output:

866
TUV MNO MNO

So here "TOM" would overlap with "UNO" ... I like @Sobrique's answer :-)

To search an array/list of contact names using the phone keypad input we can create a hash containing keys and values of the names and their number equivalents and then match the "converted" name value against the input:

use Hash::MultiValue; # makes reversing and flattening easier

my @contacts = <DATA> ;
chomp @contacts;

# build a hash from the phone_keypad array or do it manually!
my @phone_keypad = qw(0 1 2ABC 3DEF 4GHI 5JKL 6MNO 7PQRS 8TUV 9WXYZ);
my %num2let =  map { /(\d{1})(\w{3,4})/; 
               if ($2) { $1 => [ split('',$2) ] } else { 0 => [] , 1 => [] } 
               } @phone_keypad ; 

# Invert the hash using Hasj::MultiValue
my $num2let_mv = Hash::MultiValue->from_mixed(\%num2let);
my %let2num = reverse $num2let_mv->flatten ;

# create key/value pairs for contact database
my %contacts2nums ;
for $contact (@contacts) {
  $contacts2nums{$contact} = join "",  
    map {  $let2num{$_} } split('', uc $contact);
}

my $phone_input = "866";

for my $contact (keys %contacts2nums) {
 print "The text: \"$contact\" matches the input: \"$phone_input\" \n"
   if $phone_input eq $contacts2nums{$contact};
}

__DATA__
Tom
Mother
TIMTOWDI
DAD
Gordon

Output:

The text: "Tom" matches the input: "866"

A more organized approach would wrap the conversion operation in a function.


Addendum:

With a real keypad you could probably come up with a simple algorithm that could be more deterministic regarding the letter you want to associate with the number on the keypad. You could iterate through the array based on number of presses of the key: e.g. two presses on "2" would be equal to "B", etc. You'd just have to figure out how/when to move to the next character with some kind of timeout/wait value. This way you would have a more exact string on which to base your search.

G. Cito
  • 6,210
  • 3
  • 29
  • 42