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;
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;
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;
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;