Today’s musical theme has nothing to do with the tasks; it’s just that earlier this week I heard Crosby, Stills, Nash & not-at-this-point-in-time-Young’s Wasted on the Way, and it got stuck in my head on loop while I was writing this.
Onward to Perl Weekly Challenge 266!
Task 1: Uncommon Words
You are given two sentences, $line1
and $line2
.
Write a script to find all uncommon words in any order in the given two sentences. Return ('')
if none found.
A word is uncommon if it appears exactly once in one of the sentences and doesn’t appear in other sentence.
Example 1
Input: $line1 = 'Mango is sweet'
$line2 = 'Mango is sour'
Output: ('sweet', 'sour')
Example 2
Input: $line1 = 'Mango Mango'
$line2 = 'Orange'
Output: ('Orange')
Example 3
Input: $line1 = 'Mango is Mango'
$line2 = 'Orange is Orange'
Output: ('')
Approach
The straightforward way to do this is to count up the occurrences of words in each line and return a list of those that occur only once, and then run the same operation on the union of those two lists.
Raku
Once again, I drew inspiration this week from reading laurent_r’s solution to one of last week’s tasks. He used a Raku type called a Bag, which is ❝a collection of distinct elements in no particular order that each have an integer weight assigned to them signifying how many copies of that element are considered “in the bag”.❞ Really, it got me reading up on Sets, Bags, and Mixes in Raku.
sub occursOnce($line) {
# create a Bag of all words
my $all = $line.comb(/\w+/).Bag;
# create a list of words that occur once in the Bag
return $all.keys.grep({ $all{$_} == 1 });
}
sub uncommonWords($line1, $line2) {
# create a Bag of words that occur once in each line
my $all = occursOnce($line1).Bag ⊎ occursOnce($line2).Bag;
# return a list of words that occur once in that Bag
return $all.keys.grep({ $all{$_} == 1 });
}
$ raku/ch-1.raku
Example 1:
Input: $line1 = 'Mango is sweet'
$line2 = 'Mango is sour'
Output: ('sour', 'sweet')
Example 2:
Input: $line1 = 'Mango Mango'
$line2 = 'Orange'
Output: ('Orange')
Example 3:
Input: $line1 = 'Mango is Mango'
$line2 = 'Orange is Orange'
Output: ('')
View the entire Raku script for this task on GitHub.
Perl
Perl doesn’t have Bags, but it does have a Hash, and we can use that just like a Bag with a little more work. I also realized that I could just join the list of words that occurred once into a new line and reuse the occursOnce()
function again to find the words that only appear in one or the other line, not both. I didn’t go back and change my Raku implementation, though, mostly because I like using the unicode ⊎ operator.
sub occursOnce($line) {
# create a hash counting the words
my %all;
$all{$_}++ for split(/\s+/, $line);
# create a list of words that occur once in the hash
return grep { $all{$_} == 1 } keys %all;
}
sub uncommonWords($line1, $line2) {
return occursOnce(
join(' ', occursOnce($line1), occursOnce($line2))
);
}
View the entire Perl script for this task on GitHub.
Python
Then, reading the documentation for the Counter
type in the collections
module, I noticed that if you passed a list to the instantiator, it initializes the Counter
using that list, much like Raku’s Bag
.
from collections import Counter
def occursOnce(line):
# create a Counter of all words
all = Counter(line.split())
# create a list of words that occur once in the Counter
return [ word for word in list(all) if all[word] == 1 ]
def uncommonWords(line1, line2):
return occursOnce(
' '.join(occursOnce(line1) + occursOnce(line2))
)
View the entire Python script for this task on GitHub.
Task 2: X Matrix
You are given a square matrix, $matrix
.
Write a script to find if the given matrix is X Matrix
.
A square matrix is an X Matrix if all the elements on the main diagonal and antidiagonal are non-zero and everything else are zero.
Example 1
Input: $matrix = [ [1, 0, 0, 2],
[0, 3, 4, 0],
[0, 5, 6, 0],
[7, 0, 0, 1],
]
Output: true
Example 2
Input: $matrix = [ [1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
Output: false
Example 3
Input: $matrix = [ [1, 0, 2],
[0, 3, 0],
[4, 0, 5],
]
Output: true
Approach
This one is basically going through arrays and examining values. The hardest part of this problem is figuring out which elements are on the diagonals. If we have a 1×1 matrix or a 2×2 matrix, every element is on a diagonal. For a 3×3 matrix, the diagonals are elements 0 and 2 on rows 0 and 2, and element 1 on row 1. For a 4×4 matrix, the diagonals are elements 0 and 3 on rows 0 and 3, and elements 1 and 2 on rows 1 and 2.
So, for an NxN matrix, diagonal elements are 0 and N-1 for rows 0 and N-1, 1 and N-2 for rows 1 and N-2, 2 and N-3 for rows 2 and N-3 and so on until the counts overlap.
We can come up with a function to determine if an element in a matrix is on a diagonal:
isDiagonal(x, y, N):
return true if N == 1 or N == 2
return true if x == y
return true if x + y == N - 1
return false
Raku
One of the great things about Raku is you can call kv
on a List, and you’ll get back an interleaved sequence of indexes and values.
sub isDiagonal($x, $y, $N) {
return (
$N == 1 || $N == 2 || $x == $y || $x + $y == $N - 1
);
}
sub isXMatrix(@matrix) {
my $N = @matrix.elems;
for @matrix.kv -> $y, @row {
for @row.kv -> $x, $value {
# fail if diagonal values are zero or
# non-diagonal values are non-zero
return False
unless isDiagonal($x, $y, $N) == ($value != 0);
}
}
return True;
}
$ raku/ch-2.raku
Example 1:
Input: $matrix = [
[1, 0, 0, 2],
[0, 3, 4, 0],
[0, 5, 6, 0],
[7, 0, 0, 1]
]
Output: True
Example 2:
Input: $matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
Output: False
Example 3:
Input: $matrix = [
[1, 0, 2],
[0, 3, 0],
[4, 0, 5]
]
Output: True
Example 4:
Input: $matrix = [
[1, 0, 0, 0, 1],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 0, 0, 1]
]
Output: True
Example 5:
Input: $matrix = [
[1, 0, 1, 0, 1],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[1, 0, 0, 0, 1]
]
Output: False
Example 6:
Input: $matrix = [
[1, 1],
[1, 1]
]
Output: True
Example 7:
Input: $matrix = [
[1, 0],
[1, 1]
]
Output: False
View the entire Raku script for this task on GitHub.
Perl
For Perl, all we have to do is change the syntax of the for loops to loop over indices, and extract the values manually.
sub isDiagonal($x, $y, $N) {
return (
$N == 1 || $N == 2 || $x == $y || $x + $y == $N - 1
);
}
sub isXMatrix(@matrix) {
my $N = scalar @matrix;
foreach my $y ( 0 .. $#matrix ) {
my @row = @{$matrix[$y]};
foreach my $x ( 0 .. $#row ) {
my $value = $row[$x];
# fail if diagonal values are zero or
# non-diagonal values are non-zero
return 0
unless isDiagonal($x, $y, $N) == ($value != 0);
}
}
return 1;
}
View the entire Perl script for this task on GitHub.
Python
Python’s enumerate
function, however, works like Raku’s kv
.
def isDiagonal(x, y, N):
return (
N == 1 or N == 2 or x == y or x + y == N - 1
)
def isXMatrix(matrix):
N = len(matrix)
for y, row in enumerate(matrix):
for x, value in enumerate(row):
# fail if diagonal values are zero or
# non-diagonal values are non-zero
if isDiagonal(x, y, N) != (value != 0):
return False
return True
View the entire Python script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-266/packy-anderson