Perl Weekly Challenge: Week 212

Challenge 1:

Jumping Letters

You are given a word having alphabetic characters only, and a list of positive integers of the same length

Write a script to print the new word generated after jumping forward each letter in the given word by the integer in the list. The given list would have exactly the number as the total alphabets in the given word.

Example 1
Input: $word = 'Perl' and @jump = (2,22,19,9)
Output: Raku

'P' jumps 2 place forward and becomes 'R'.
'e' jumps 22 place forward and becomes 'a'. (jump is cyclic i.e. after 'z' you go back to 'a')
'r' jumps 19 place forward and becomes 'k'.
'l' jumps 9 place forward and becomes 'u'.
Example 2
Input: $word = 'Raku' and @jump = (24,4,7,17)
Output: 'Perl'

First we split the word into an array of its' constituent letters.

my @letters = $word.comb;

Then for each letter...

for 0 .. @letters.end -> $i {

...we are going to find it's relative position in the alphabet. In order to do that we are going to need the numeric value of a which we can do with the .ord() method. A small complication is that the capital and the small letters are in two seperate ranges so we need the value of either A or a depending on if the letter is capital or not.

    my $aord =  @letters[$i] ~~ /<upper>/ ?? 'A'.ord !! 'a'.ord;

Now (again with .ord()) we can find the value of the current letter, subtract the value of A (or a) from it and the value of the current jump. This should give a number between 0-25 but just in casing adding the jump value caused it to go over, we take the result modulo 26. This amount is then added to the value of a (or A) to give the relative position in the alphabet. .chr() is the opposite of .ord(). It will take that relative position and transform it back into a character.

    @letters[$i] = 
    ((((@letters[$i].ord - $aord) + @jump[$i]) % 26) + $aord).chr;
}

After we have transformed every letter to its' new value, the last step is to join them all up and print the new word.

say @letters.join;

(Full code on Github.)

This is the translation into Perl.

my @letters = split //, $word;
for my $i (0 .. scalar @letters - 1) {
    my $aord =  $letters[$i] =~ /[[:upper:]]/ ? ord 'A' : ord 'a'; 
    @letters[$i] = 
        chr(((((ord $letters[$i]) - $aord) + $jump[$i]) % 26) + $aord);
}

say join q{}, @letters;

(Full code on Github.)

Challenge 2:

Rearrange Groups

You are given a list of integers and group size greater than zero.

Write a script to split the list into equal groups of the given size where integers are in sequential order. If it can’t be done then print -1.

Example 1
Input: @list = (1,2,3,5,1,2,7,6,3) and $size = 3
Output: (1,2,3), (1,2,3), (5,6,7)
Example 2
Input: @list = (1,2,3) and $size = 2
Output: -1
Example 3
Input: @list = (1,2,4,3,5,3) and $size = 3
Output: (1,2,3), (3,4,5)
Example 4
Input: @list = (1,5,2,6,4,7) and $size = 3
Output: -1

I had a couple of false starts with this one before I got it right.

As we begin, we can quickly dispose of one case. If the number of values in the list is not evenly divisible by the size of the groups we want, it's not going to work so we can just quit there and then.

unless @list.elems %% $size {
    invalid;
}

As there are a number of cases which can give an invalid result, I wrote a little reusable function that just prints -1 and exits the script.

sub invalid {
    say -1;
    exit;
}

Back to the main function, the next step is to gather all the elements in @list and add them to a hash where the keys are the values of each element and the values are the number of times each particular value occurs.

my %frequency;
for @list -> $i {
    %frequency{$i}++;
}

We will a number of variables to store data.

An array of the keys of that hash (i.e. the unique values in @list) sorted in numeric order.

my @numbers = %frequency.keys.sort({ $^a <=> $^b });

A list to store the results.

my @results;

The number of groups that should be created.

my $length = @list.elems div $size;

For each group...

for 1 .. $length {

...We need a list to store the group.

    my @group;

We also need to need to know which number in @numbers to start adding to the grup. We start from the 0th element and continue traversing through @numbers until we find an element which is a key in %frequency with a value greater than zero.

    my $start = 0;

    for @numbers -> $i {
        if %frequency{$i} > 0 {
            $start = $i;
            last;
        } 
    }

If we get all the way through @numbers, it means there are no more keys in %frequency with non-zero values so time to throw in the towel.

    if !$start {
        invalid;
    }

From $start we count upwards by one and add the value of that key in %frequency to group...

    for $start ..^ $start + $size -> $i {

...Unless the key doesn't exist or its value is 0. In that case the group (and therefore the whole @list) is invalid.

        if %frequency{$i}:!exists || %frequency{$i} == 0 {
            invalid;
        } 

        @group.push($i);

Once a value has been added, its' key in %frequency is decremented.

        %frequency{$i}--;
    }

After a group has been created, it is added to @results.

    @results.push(@group);
}

Once all the groups have been created, we can print them out. The slightly convuluted line below looks that way in order to match the format of the output in the examples.

@results.map({ q{(} ~ @$_.join(q{, }) ~ q{)} }).join(q{, }).say;

(Full code on Github.)

And this is the Perl version.

sub invalid {
    say -i;
    exit;
}

my $size = shift;
my @list = @ARGV;

unless (scalar @list % $size == 0) {
    invalid;
}

my %frequency;
for my $i (@list) {
    $frequency{$i}++;
}

my @numbers = sort { $a <=> $b } keys %frequency;
my @results;
my $length = scalar @list / $size;

for (1 .. $length) {
    my @group;
    my $start = 0;

    for my $i (@numbers) {
        if ($frequency{$i} > 0) {
            $start = $i;
            last;
        } 
    }

    if (!$start) {
        invalid;
    }

    for my $i ($start .. $start + $size - 1) {
        if (!exists $frequency{$i} || $frequency{$i} == 0) {
            invalid;
        } 

        push @group, $i;
        $frequency{$i}--;
    }

    $length += $size;
    push @results, \@group;
}

say join q{, }, map { q{(} . ( join q{, }, @{$_} ) . q{)} } @results;

(Full code on Github.)