Perl Weekly Challenge: Week 395
Challenge 1:
Complex Number
You are given two complex numbers as string.
Write a script to return the multiplication of the given complex numbers.
Using the formula: (a + bi) x (c + di) = (ac - bd) + (ad + bc)i
Example 1
Input: $num1 = "1+1i", $num2 = "1+1i"
Output: "0+2i"
Example 2
Input: $num1 = "1+(-1)i", $num2 = "1+(-1)i"
Output: "0+(-2)i"
Example 3
Input: $num1 = "0+3i", $num2 = "0+4i"
Output: "-12+0i"
Example 4
Input: $num1 = "3+4i", $num2 = "3+(-4)i"
Output: "25+0i"
Example 5
Input: $num1 = "50+(-20)i", $num2 = "-10+30i"
Output: "100+1700i"
Ugh maths! but luckily Raku has a datatype for complex numbers so this should be easy right?
sub MAIN(
The actual arguments to the script (which we get from the command-line,) are of
type ComplexStr which is a dual complex number snd string.
ComplexStr $num1, #= a complex number
ComplexStr $num2 #= a complex number
) {
We muitiply the two complex numbers and print the result.
say $num1 * $num2;
}
Sadly, it's never that simple. This will work for examples 1 or 3 but fail for
any example where the imaginary part of a number is negative, e.g. example 2. It
will work if we change the input to 1-1i.
The following function will reformat the input to something more palatable to Raku.
sub normalize(Str $num) {
$num.match(/
$<real> = ['-'? \d+]
\+
[\(? $<imaginary> = ['-'? \d+] \)?]
i
/);
return ($<real>,$<imaginary> < 0 ?? '-' !! '+', $<imaginary>.abs,'i').join;
}
MAIN() will now look like this:
say normalize($num1) * normalize($num2);
It's still not quite right. This gives 0-2i for example 2 and the spec expects
0+(-2)i. This function fixes the problem.
sub denormalize(Complex $num) {
return $num.im < 0 ?? ($num.re, "+(", $num.im, ")i").join !! $num;
}
The final form of MAIN() will look like this:
say denormalize(normalize($num1) * normalize($num2));
For the Perl version, not having a builtin Complex type is actually a blessing. Instead of all that formatting to and fro, we just use the method suggested in the spec.
We still need a regular expression to parse the real and imaginary parts of a complex
number from the string that contains it. I store it in a variable $complex so it can
be reused.
my $complex = qr/
(?<real> \-? \d+)
\+
\(? (?<imaginary> \-? \d+) \)?
i
/msx;
Then for both $num1 and $num2 we find the real and imaginary parts and assign
them to four variables $a, $b, $c and $d.
$num1 =~ $complex;
my $a = $+{real};
my $b = $+{imaginary};
$num2 =~ $complex;
my $c = $+{real};
my $d = $+{imaginary};
Now we can apply the formula from the spec.
my $real = ($a * $c - $b * $d);
my $imaginary = ($a * $d + $b * $c);
One last touch is to put parentheses around the imaginary number if it is negative.
if ($imaginary < 0) {
$imaginary = "($imaginary)";
}
We print the result.
say join q{}, ($real, '+', $imaginary, 'i');
Challenge 2:
Word Squares
You are given a list of equal lengths words.
Write a script to arrange a set of words into a grid that reading across any row gives the exact same word as reading down its corresponding column.
Example 1
Input: @words = ("MA", "AM", "MY", "ME")
Output: ("MA", "AM")
M A
A M
Example 2
Input: @words = ("SUN", "USE", "NET", "SEE", "TEN")
Output: ("SUN", "USE", "NET")
S U N
U S E
N E T
Example 3
Input: @words = ("BOAT", "TEAL", "AREA", "TSAR", "OGRE")
Output: ("BOAT", "OGRE", "AREA", "TEAL")
B O A T
O G R E
A R E A
T E A L
Example 4
Input: @words = ("CARD", "AREA", "REAR", "DART")
Output: ("CARD", "AREA", "REAR", "DART")
C A R D
A R E A
R E A R
D A R T
Example 5
Input: @words = ("HEART", "TREND", "EMBER", "RESIN", "ABUSE")
Output: ("HEART", "EMBER", "ABUSE", "RESIN", "TREND")
H E A R T
E M B E R
A B U S E
R E S I N
T R E N D
First we need to know the size of the word square we are going to make. As it is a square the number of row and columns in the grid will be the same and equal to the number of characters in the first word (or any word really; the spec says all words are of the same length.)
my $size = @words[0].chars;
We will also need to keep track of the grid and the words used to create it.
my @grid;
my %used;
We construct the word square by doing a recursive depth-first search in search()
sub search(Str $prefix) {
If the number or rows equals the number of columns, we are done.
if @grid.elems == $size {
return True;
}
Otherwise, we look through the words for candidates which have the same prefix as the rows already chosen.
for @words.grep({ $_.starts-with($prefix) }) -> $word {
If the word was already used, skip it and move on to the next one.
if %used{$word}:exists {
next;
}
Otherwise add it to the list of used words.
@grid.push($word);
%used{$word} = True;
The prefix of the next row must match the next column of every previously chosen row.
my $column = @grid.elems;
my $nextPrefix = @grid.map({ $_.substr($column, 1) }).join;
If the next prefix is suitable, we use it to recursively call search() again.
if search($nextPrefix) {
return True;
}
Otherwise we have to backtrack so another word can be tried in this position. We take the current word out of the grid and remove it from the used list.
@grid.pop;
%used{$word}:delete;
}
If somehow we have reached here, no word square is possible so we just retuen False.
return False;
}
We kick of the search with an empty string.
search(q{});
Once search() is finished, we should have a proper word square in @grid. We
print it out in the manner suggested by the spec.
say q{(}, @grid.map({ "\"$_\"" }).join(q{, }), q{)};
This is the Perl version which is almost exactly similar to Raku.
my @words = @ARGV;
my $size = length $words[0];
my @grid;
my %used;
sub search($prefix) {
if (scalar @grid == $size) {
return true;
}
for my $word (grep { /^$prefix/ } @words) {
if (exists $used{$word}) {
next;
}
push @grid, $word;
$used{$word} = true;
my $column = scalar @grid;
my $nextPrefix = join q{}, map { substr $_, $column, 1 } @grid;
if (search($nextPrefix)) {
return true;
}
pop @grid;
delete $used{$word};
}
return false;
}
search(q{});
say q{(}, (join q{, }, map{ "\"$_\"" } @grid), q{)};