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;
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; });
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; });
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]};
}
The elements of each row of @transposed
are joined with commas and printed.