Validly Transposed Raku and Perl

by Arne Sommer

Validly Transposed Raku and Perl

[126] Published 2. May 2021.

This is my response to the Perl Weekly Challenge #110.

Challenge #110.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

Note the discrepancy between the first rule (plus, digit, digit, space, space, …) and the values given in the in- and output (space, plus, digit, digit, space, …). I am assuming that the latter is correct.

We need a file with the phone numbers. Here it is:

File: phone-numbers.txt
0044 1148820341
 +44 1148820341
  44-11-4882-0341
(44) 1148820341
  00 1148820341

The program is quite straight forward:

File: valid-phone-numbers
#! /usr/bin/env raku

unit sub MAIN ($phonebook where $phonebook.IO.f && $phonebook.IO.r  # [1]
                = 'phone-numbers.txt');

for $phonebook.IO.lines -> $line                                    # [2]
{
  next unless $line.chars == 15;                                    # [3]
  next unless $line.substr(4,1) eq ' ';                             # [4]
  next unless $line.substr(5) ~~ /^\d ** 10$/;                      # [5]


  my $first = $line.substr(0,4);                                    # [6]

  next unless $first ~~ /^\d ** 4$/   ||                            # [7]
              $first ~~ /^\s\+\d\d$/  ||                            # [7a]
              $first ~~ /^\(\d\d\)$/;                               # [7b]

  say $line;                                                        # [8]
}

[1] Ensure that we get a file name (IO.f), and that it is readable (IO.r). Note the default file name.

[2] Iterate over the lines in the file.

[3] Skip lines that do not have the correct length of 15 characters.

[4] Skip lines where the fifth character (at offset 4) is anything besides a space character.

[5] Skip lines where the last part (after the space mentioned in [4]) does not consist of digits only.

[6] We are considering the first four characters next. Pick them out as a string.

[7] Skip lines where none of the regular expressions are satisifed. The first on looks for four digits [7], the second for a space, a plus and two digits [7a], and the third for two dogots inside a pair of parens [7b].

[8] It must be legal; print it.

See docs.raku.org/routine/f and docs.raku.org/routine/r for more information about those file tests.

Running it gives the expected result:

$ ./valid-phone-numbers 
0044 1148820341
 +44 1148820341
(44) 1148820341

A Perl Version

This is straight forward translation of the Raku version.

File: valid-phone-numbers-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use File::Slurp;

my $phonebook  = $ARGV[0] // 'phone-numbers.txt';

for my $line (read_file($phonebook, chomp => 1))
{
  next unless length($line) == 15;
  next unless substr($line, 4, 1) eq ' ';
  next unless substr($line, 5) =~ /^\d{10}$/;

  my $first = substr($line, 0, 4);

  next unless $first =~ /^\d{4}$/      ||
              $first =~ /^\s\+\d\d$/   ||
              $first =~ /^\(\d\d\)$/;

  say $line;
}

Running it gives the same result as the Raku version:

$ ./valid-phone-numbers-perl
0044 1148820341
 +44 1148820341
(44) 1148820341

Challenge #110.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

This is just a matter of reading the lines, putting the words in separate lists as we do som, and printing those list at the end.

File: transpose-file
#! /usr/bin/env raku

unit sub MAIN ($csv-file where $csv-file.IO.f && $csv-file.IO.r
  = 'persons.csv');

my @lines;

for $csv-file.IO.lines -> $line
{
  my @words = $line.split(',');              # [1]

  for 0 .. @words.end -> $index              # [2]
  {
     @lines[$index].push: @words[$index];    # [3]
  }
}

for @lines -> $line                          # [4]
{
  say $line.join(',');                       # [4a]
}

[1] Split the line on comma.

[2] Iterate over the index in the list of words we just got.

[3] Add the word to the sublist with that index.

[4] For each element in the list, print the values (the sublist).

Running it:

./transpose-file
name,Mohammad,Joe,Julie,Cristina
age,45,20,35,10
sex,m,m,f,f

Looking good.

Note that the challenge does not say what to do if the number of words (commas) on the lines differ. So I have ignored it.

Perl

This is a straight forward(ish) translation of the Raku version.

File: transpose-file-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use File::Slurp;

my $csv_file  = $ARGV[0] // 'persons.csv';

my @lines;

for my $line (read_file($csv_file, chomp => 1))
{
  my @words = split(',', $line);

  for my $index (0 .. @words -1)
  {
     $lines[$index] .= $lines[$index]  # [1]
       ? ( "," . $words[$index])       # [1a]
       : $words[$index];               # [1b]
  }
}

say $_ for @lines;                     # [2]

[1] Lists of lists require pointers in Perl. It is easier to use strings. If we add a value, prefix it with a comma [1a]. If not, just add it.

[2] Much shorter, as we just print a bunch of strings.

Running it gives the same result as the Raku version:

$ ./transpose-file-perl
name,Mohammad,Joe,Julie,Cristina
age,45,20,35,10
sex,m,m,f,f

And that's it.