Perl Weekly Challenge: Week 110

Challenge 1:

Valid Phone Numbers

You are given a text file.

Write a script to display all valid phone numbers in the given text file.

Acceptable Phone Number Formats
+nn  nnnnnnnnnn
(nn) nnnnnnnnnn
nnnn nnnnnnnnnn
Input File
0044 1148820341
+44 1148820341
44-11-4882-0341
(44) 1148820341
00 1148820341
Output
0044 1148820341
+44 1148820341
(44) 1148820341

I spent a good part of the '90s using Perl to do this kind of thing.

    my $filename = shift // die "Need a filename\n";

We abort the script if not provided with a file name.

    open my $file, '<' , $filename or die "$OS_ERROR\n";
    local $RS = undef;
    my @phone_numbers = split "\n", <$file>;
    close $file;

The file is opened. By setting $RS to undefined, the entire file is slurped up in one go rather than read line by line. This probably doesn't matter for this input but it is more efficient for large files.

    my $valid = qr{ \A \s* ( \+\d{2} | \(\d{2}\) | \d{4} ) \s* \d{10}  \s* \z }msx;

Looking at the acceptable formats, we have three different formats for the area code while the 10 digit phone number is always the same. The regexp above should properly capture all this. It is compiled into a variable for some efficiency.

    map { say; } grep { /$valid/ } @phone_numbers;

(Full code on Github.)

We grep through the @phone_numbers using our regexp and then any matches are fed into say via a map.

This is the Raku version. The regexp syntax is a little different and the ability to chain methods together helps in readability but otherwise it is nearly the same as in Perl.

    my $valid = rx{ 
        ^ \s* ( \+\d ** 2 | \(\d ** 2\) | \d ** 4 ) \s* \d ** 10  \s* $
    };

    $filename.IO.lines.grep({ /$valid/ }).map({ .say; });

(Full code on Github.)

I read the file line by line this time but I have been told IO.lines is very efficient so slurping it all in one go doesn't buy you very much compared to in Perl.

Challenge 2:

Transpose File

You are given a text file.

Write a script to transpose the contents of the given file.

Input File
name,age,sex
Mohammad,45,m
Joe,20,m
Julie,35,f
Cristina,10,f
Output:
    name,Mohammad,Joe,Julie,Cristina
    age,45,20,35,10
    sex,m,m,f,f

Raku first.

    my @table;
    for $filename.IO.lines -> $line {
        @table.push($line.split(q{,}));
    }

The file provided on the command line is read line by line. Each line is split based on commas and the resulting array is added into another array. The result, @tables, is a two-dimwensional matrix of fields.

    (0 ..^ @table[0].elems).map({ @table[*;$_]; }).map({ $_.join(q{,}).say; });

(Full code on Github.)

To transpose that matrix into the order required, a series of "zen" slices of @tables equal to the number of columns are taken and .map()ped into an anonymous array. Via another .map(), the fields of each row of that array are joined with commas and printed.

The Perl version isn't nearly as compact.

    my $filename = shift // die "Need a filename\n";

    open my $file, '<' , $filename or die "$OS_ERROR\n";
    local $RS = undef;
    my @input = split "\n", <$file>;
    close $file;

The same method of slurping up the input file is used as in challenge 1.

    my @table;

    for my $line (@input) {
        push @table, [ split /,/, $line ];
    }

The 2d array @table is created just as in Raku albeit more verbosely.

    my @transposed;

    for my $j (0 .. scalar @{$table[0]} - 1) {
        for my $i (0 .. scalar @table - 1) {
            push @{$transposed[$j]}, $table[$i]->[$j]; 
        }
    }

Array slices are not as powerful in Perl so @table is transposed by copying each element into a new array, @transposed, via a double loop.

    for my $i (0 .. scalar @transposed - 1) {
        say join q{,}, @{$transposed[$i]};
    }

(Full code on Github.)

The elements of each row of @transposed are joined with commas and printed.