I’m sorry for the lack of music this time around, but when I read “broken keys”, there’s only one broken key I can think of.
Let’s proceed with caution and approach Perl Weekly Challenge 275!
Task 1: Broken Keys
You are given a sentence, $sentence
and list of broken keys @keys
.
Write a script to find out how many words can be typed fully
.
Example 1
Input: $sentence = "Perl Weekly Challenge", @keys = ('l', 'a')
Output: 0
Example 2
Input: $sentence = "Perl and Raku", @keys = ('a')
Output: 1
Only Perl since the other word two words contain 'a' and can't be typed fully.
Example 3
Input: $sentence = "Well done Team PWC", @keys = ('l', 'o')
Output: 2
Example 4
Input: $sentence = "The joys of polyglottism", @keys = ('T')
Output: 2
Approach
This screams regular expression to me: build a regular expression of the letters for the broken keys, split the sentence into words on whitespace, then match the words that don’t contain the letters, and count them up. Easy-peasy.
Raku
sub brokenKeys($sentence, @keys) {
my @words = $sentence.comb(/\S+/);
my $regex = '<[' ~ @keys.join ~ ']>';
return ( ( @words.grep({! /:i <$regex>/ }) ).elems )
}
$ raku/ch-1.raku
Example 1:
Input: $sentence = "Perl Weekly Challenge", @keys = ('l', 'a')
Output: 0
Example 2:
Input: $sentence = "Perl and Raku", @keys = ('a')
Output: 1
Example 3:
Input: $sentence = "Well done Team PWC", @keys = ('l', 'o')
Output: 2
Example 4:
Input: $sentence = "The joys of polyglottism", @keys = ('T')
Output: 2
View the entire Raku script for this task on GitHub.
Perl
sub brokenKeys($sentence, @keys) {
my @words = split /\s+/, $sentence;
my $regex = '[' . join('', @keys) . ']';
return ( scalar( grep {! /$regex/i } @words) );
}
View the entire Perl script for this task on GitHub.
Python
import re
def brokenKeys(sentence, keys):
words = sentence.split()
regex = re.compile('[' + ''.join(keys) + ']', re.I)
return( len([
word for word in words if not re.search(regex, word)
]) )
View the entire Python script for this task on GitHub.
Elixir
def brokenKeys(sentence, keys) do
{:ok, reg} = Regex.compile("[" <> Enum.join(keys) <> "]", [:caseless])
String.split(sentence)
|> Enum.filter(fn word -> not Regex.match?(reg, word) end)
|> Enum.count
|> to_string
end
View the entire Elixir script for this task on GitHub.
Task 2: Replace Digits
You are given an alphanumeric string, $str
, where each character is either a letter or a digit.
Write a script to replace each digit in the given string with the value of the previous letter plus (digit) places.
Example 1
Input: $str = 'a1c1e1'
Ouput: 'abcdef'
shift('a', 1) => 'b'
shift('c', 1) => 'd'
shift('e', 1) => 'f'
Example 2
Input: $str = 'a1b2c3d4'
Output: 'abbdcfdh'
shift('a', 1) => 'b'
shift('b', 2) => 'd'
shift('c', 3) => 'f'
shift('d', 4) => 'h'
Example 3
Input: $str = 'b2b'
Output: 'bdb'
Example 4
Input: $str = 'a16z'
Output: 'abgz'
Approach
The fourth example helped me figure out how to interpret multiple numeric digits in a row. The description didn’t really make clear to me what 'a16z'
should produce. The 16th letter after a? Then the output would be 'aqz'
. But the output is 'abgz'
, so obviously, we’re interpreting each numeric digit separately. But what should the previous letter be for each digit? The 1 is obvious: it’s adding 1 to the 'a'
to produce 'b'
. But should 6 be added to the 'a'
or the 'b'
we just produced? Well, if we count back six places from 'g'
, we find that we get 'a'
, so we know that we’re supposed to add the digit to the previous letter in the original string, not the previous letter in the output string.
One thing that’s implied by the definition, and none of the examples provide an alternate explanation: the first character of the string has to be a letter, because if it was a numeric digit, we wouldn’t have a previous letter to add it to.
Raku
sub replaceDigits($str) {
my @chars = $str.comb;
my $last_letter = @chars[0];
my $out;
for @chars -> $c {
if ($c ~~ /<[0..9]>/) {
$out ~= ( $last_letter.ord + $c.Int ).chr;
}
else {
$out ~= $c;
$last_letter = $c;
}
}
return $out;
}
$ raku/ch-2.raku
Example 1:
Input: $str = 'a1c1e1'
Output: 'abcdef'
Example 2:
Input: $str = 'a1b2c3d4'
Output: 'abbdcfdh'
Example 3:
Input: $str = 'b2b'
Output: 'bdb'
Example 4:
Input: $str = 'a16z'
Output: 'abgz'
View the entire Raku script for this task on GitHub.
Perl
sub replaceDigits($str) {
my @chars = split //, $str;
my $last_letter = $chars[0];
my $out;
foreach my $c ( @chars ) {
if ($c =~ /[0-9]/) {
$out .= chr( ord($last_letter) + $c );
}
else {
$out .= $c;
$last_letter = $c;
}
}
return $out;
}
View the entire Perl script for this task on GitHub.
Python
Python has the handy str.isnumeric()
function to tell us if a character is numeric.
def replaceDigits(str):
last_letter = str[0:1]
out = ''
for c in str:
if c.isnumeric():
out += chr( ord(last_letter) + int(c) )
else:
out += c
last_letter = c
return out
View the entire Python script for this task on GitHub.
Elixir
With Elixir I’m using the pattern I’ve fallen into of having a version of the function that unpacks the data as it’s passed in that recursively calls a version of the same function with more parameters (one of which is a list we’re processing) to track the things we need to track, then a stopping case where the list is empty where we just return the final output.
Converting to ASCII values and back to characters was a bit different: I chose to use the utf8
modifier on the binary syntax << >> to extract the ASCII value, and then use List.to_string/1
to convert it back.
def replaceDigits([], _, out), do: out
def replaceDigits(, last, out) do
if Regex.match?(~r/[0-9]/, c) do
<<chrval::utf8>> = last
c = List.to_string([ chrval + String.to_integer(c) ])
replaceDigits(rest, last, out <> c)
else
replaceDigits(rest, c, out <> c)
end
end
def replaceDigits(str) do
replaceDigits(String.graphemes(str), nil, "")
end
View the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-275/packy-anderson