📘 Separating groups of digits using Perl 6

Put commas between the three-digit groups in a big number.

The task is to print an integer number, for example, 1234567890,in the form of 1,234,567,890.

Here is a possible solution that uses a lot of Perl 6 facilities:

$n ~~ s/<?after \d> (\d ** 3)+ $/{$0.map(',' ~ *).join}/;

On the top level, we’ve got a substitution s///. The pattern is anchored to the end of the string: s/...$//. From the end of the string, groups of three digits are searched: (\d ** 3)+. A single \d matches a digit, and the ** quantifier requires exactly three of them. The second quantifier, +, allows more than one such groups.

A dot may only follow at least one digit. To avoid converting six-digit numbers to something like .123.456, a look-behind assertion <?after \d> is inserted, which insists that there is a digit before the first group of three digits. The whole replacement only happens if there are more than four digits in the original number.

The second part of the substitution is an executable code: s//{...}/. After a successful match, the three-digit groups appear in the match object $0. Because of the + quantifier, you can treat this object as an array.

First, each element is translated to a string with a comma in front of it: map(',' ~ *). Using the * character creates a WhateverCode block, which is equivalent to {',' ~ $_}. It is also possible to rewrite the map operation using the string interpolation: map({",$_"}).

Finally, transformed parts are joined together, using the join method call. The $n variable now contains a string with the desired result.

📘 Separating digits and letters using Perl 6

In a given string that contains letters and digits, insert dashes on the borders between the digit and letter sequences.

The goal of the task is to convert, for example, the string 6TGT68 to 6-TGT-68. With some modification, this task may be needed for creating the canonical form of car license plates in some countries.

There are character classes in the Perl 6 regexes: <:alpha> for alphabetical characters and <:digit> for digits. Let us use them for finding the borders between digits and letters. In other words, we need to find all the sequences of two characters, where one of them is a letter and another is a digit.

my $s = '6TGT68';

$s ~~ s:g/
    (<:alpha>) (<:digit>) |
    (<:digit>) (<:alpha>)   
/$0-$1/;

say $s; # 6-TGT-68

The replacement construct s:g/// is applied globally. It finds all the places, which match either <:alpha> <:digit> or <:digit><:alpha>.

When the match is found, the $0 and $1 variables are set to either a letter and a digit or to a digit and a letter. The parentheses indices count from zero in each alternative separated with a vertical bar.

The replacement uses the found characters and inserts a hyphen character between them. Notice that the spaces in the regex are allowed and ignored, while the spaces in the replacement part are significant, so the /$0-$1/ part should not contain additional spaces.

📘 Removing duplicated words using Perl 6

Remove repeated words from froma sentence.

Repeated words are most often unintended typing mistakes. In rare cases, though, this is correct like with the word that:

He said that that tree is bigger

Anyway, let us remove the double words ignoring the grammar for now. To find if the word is repeated, a regex with variables can be used. Then, using a substitution, only one copy of a word is passed to the resulting string.

my $string = 'This is is a string';
$string ~~ s:g/ << (\w+) >> ' ' << $0 >> /$0/;

say $string;

The regex part of the sroutine is a regex that is first looking for a word (as a sequence of word characters \w+) and its copy after a space. The first occurrence is saved in the $0 variable, which is immediately used in the same regex. It is also used in the replacement part.

To prevent repetitions, the word-edge anchors are used: << for the beginning of a word and >> for its end. In the given example, this prevents treating the last two letters of the word This as a separate word, is, and thus, the correct phrase This is a string will not be broken after the substitution.

Notice that non-literal spaces in a regex are not taking part in string matching, although, they are necessary in a sequence << (\w+) >>. The construction <<(\w+)>> is a syntax error as it is similar to the character class <[...]> or a reference to a named regex like <:alnum>, and the compiler prefers explicit spaces in this case.

📘 Doubling characters using Perl 6

In a given string, double each alphanumeric character and print the result. Punctuation and spaces should stay untouched.

Regexes are very powerful tools for searching and replacing texts. In this task, only the alphanumeric characters are requested to be doubled. The \w character class is the perfect match to find these characters.

my $string = 'Hello, 1 World!';
$string ~~ s:g/(\w)/$0$0/;
say $string;

We are using the s/// construct here. The $string is matched against the \w regex. The parentheses around \w capture the found character. It is now contained in the $0 special variable.

In the second part of the replacement, $0 is used twice, and, thus, the doubled character is replacing the single character found in the string. The :g adverb makes the replacement global.

The program prints the following output:

HHeelllloo, 11 WWoorrlldd!

Let us update the program to exclude the r letter from the process. In other words, all the characters, except r, must be doubled. Build a new character class as a difference between \w and r as demonstrated in the following line of code:

$string ~~ s:g/(<[\w] - [r]>)/$0$0/;

Now, the output is a bit different:

HHeelllloo, 11 WWoorldd!

📘 Currency converter written in Perl 6

Parse the string with a currency converting request such as ‘10 EUR in USD’ and print the result.

The task of understanding free text is quite complicated. For the currency conversion, we can create a simple regex that matches the most common quires.

Let’s ignore the way the exchange rate data are obtained and use the hard-coded values:

my %EUR =
    AUD => 1.4994,  CAD => 1.4741,
    CHF => 1.1504,  CNY => 7.7846,
    DKK => 7.4439,  GBP => 0.89148,
    ILS => 4.1274,  JPY => 131.94,
    RUB => 67.471,  USD => 1.1759;

This hash contains the exchange rates of a few currencies to Euro. Thus, it is possible to directly use it to get the results for any pair with EUR, such as:

10 EUR in GBP
20 ILS toEUR

For other combinations, a cross-rate can be used. For example, the request to convert JPY to CHF uses the values of JPY-to-EUR and EUR-to-CHF conversion.

Here is a regex that parses the textual request:

$request ~~ 
    /(<[\d .]>+) \s* (<:alpha> ** 3) .* (<:alpha> ** 3)/;

Let’s also make a loop to accept multiple requests from the keyboard.

Here is the program:

while (my $request = prompt('> ')) {
    $request ~~ 
        /(<[\d .]>+) \s* (<:alpha> ** 3) .* (<:alpha> ** 3)/;
    my ($amount, $from, $to) = $0, $1, $2;
    
    my $result = 0;
    if $to eq 'EUR' {
       $result = $amount / %EUR{$from};
    }
    elsif $from eq 'EUR' {
       $result = $amount * %EUR{$to};
    }
    else {
       $result = $amount * %EUR{$to} / %EUR{$from};
    }

    say "$amount $from = $result $to";
}

After the regex match, the values are copied to the three variables: $amount$from, and $to. The ifelsifelse chain is used to choose how the new amount is calculated: either as direct or cross rate.

Run the program and enter different requests with both integer and floating-point values for different combinations of the currency codes listed in the %EUR hash.

Here are a few ideas of how to improve the program: 

  1. Check if the entered currency code exists.
  2. Make the request case-insensitive.
  3. Create another hash with exchange rates against USD and choose it, whenever possible, to avoid cross-calculations.

📘 Skipping Pod documentation in Perl 6

Create the program that copies the input text and skips the documentation in the Pod style that starts with =begin and ends with =end.

Let us take a simple text containing a piece of Pod documentation:

# Hello, World!
=begin
This program prints a message
=end
say 'Hello, World!';

The program should read it and print everything that is not the comment. Thus, for the given example, only the first and the last lines can go to the output.

In Perl 6, there is a family of flipflop operators that, although being a bit hard to understand at first, are quite powerful for introducing the two-state triggers in the program flow.

while $_ = $*IN.get {
    .say unless /^'=begin'/ ff /^'=end'/;
}

Reading lines from the STDIN handle is the same as it will be done it in task 95, The catutility. The flipflop construct /^'=begin'/ ff /^'=end'/ is False initially and becomes True as soon as the $_ content matches the first regex. After that, it stays True until the second condition is True. In the end, the unless clause only passes the lines that are not located between the =begin and =end instructions. The .say method call is thus printing all the remaining lines.

📘 Count words using Perl 6

Count the number of words in a text.

Before solving the task, let us assume that by words we mean here a sequence of alphanumeric characters, including the underscore symbol.

Here is the solution:

my $text = prompt('Text> ');
say $text.comb(/\w+/).elems;

Try it on a few test inputs:

$perl6 countwords.pl 
Text> Hello, World;
2

The program uses regexes for extracting words using the \w character class and the combstring method that returns a sequence of the words:

$text.comb(/\w+/)

The + quantifier allows a repetition of \w, so it matches the whole word. 

Alternatively, a more traditional match with a regex may be used:

$text ~~ m:g/(\w+)/;
say $/.elems;

Parentheses in the regex capture the word, and the :g adverb applies it a few times until all the words are found. The $/ variable (called the match object) keeps all the matched substrings, and the elems method returns the number of elements in it.