This week the tasks are all about characters ch-ch-ch-changing, so of course I needed the musical theme to be David Bowie’s Changes.
So let’s be quite aware as we’re going through Perl Weekly Challenge 282!
Task 1: Good Integer
You are given a positive integer, $int
, having 3 or more digits
.
Write a script to return the Good Integer
in the given integer or -1
if none found.
A good integer is exactly three consecutive matching digits.
Example 1
Input: $int = 12344456
Output: "444"
Example 2
Input: $int = 1233334
Output: -1
Example 3
Input: $int = 10020003
Output: "000"
Approach
At first, I thought I wanted to use a regular expression, but I couldn’t figure out how to make sure I didn’t match four or more of the same character. For example, (\d)\1\1(?!\1)
would match a numeric character followed by two more of that character, followed by something OTHER than that character. This winds up matching 333
, but it fails overall because it also matches 3333
starting at the second character. So, rather than bend my brain too much trying to make it work with regular expressions, I took the easy route: split the string into characters and process it character by character, counting them along the way. Each time the character changes, I check to see what the count for the last character is, and if it’s three, we’ve found our solution.
If I’d been clever enough, I could have found a way to make regexes work like Robbie Hatley did.
Raku
This solution uses all the usual tricks: comb
to split the string into characters, casting $int
as a Str to convert it to a string, using a postfix .shift
to fetch the first character.
sub goodInteger($int) {
my @chars = $int.Str.comb;
my $good = @chars.shift;
my $count = 1;
for @chars -> $c {
if ($c ne $good) {
# the character changed!
if ($count == 3) {
last;
}
# restart the count with new character
$good = $c;
$count = 1;
}
else {
# the character is the same
$count++;
}
}
if ($count == 3) {
return '"' ~ ($good x $count) ~ '"';
}
return -1;
}
View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: $int = 12344456
Output: "444"
Example 2:
Input: $int = 1233334
Output: -1
Example 3:
Input: $int = 10020003
Output: "000"
Perl
sub goodInteger($int) {
my @chars = split //, "$int";
my $good = shift @chars;
my $count = 1;
foreach my $c ( @chars ) {
if ($c ne $good) {
# the character changed!
if ($count == 3) {
last;
}
# restart the count with new character
$good = $c;
$count = 1;
}
else {
# the character is the same
$count++;
}
}
if ($count == 3) {
return '"' . ($good x $count) . '"';
}
return -1;
}
View the entire Perl script for this task on GitHub.
Python
This time I remembered the Python idiom list(string)
to convert a string into a list of characters.
def goodInteger(intVal):
chars = list(str(intVal))
good = chars.pop(0)
count = 1
for c in chars:
if c != good:
# the character changed!
if count == 3:
break
# restart the count with new character
good = c
count = 1
else:
# the character is the same
count += 1
if count == 3:
return '"' + (good * count) + '"'
return -1
View the entire Python script for this task on GitHub.
Elixir
I’m happy that I’m remembering that I can pipe ( |> ) into the first parameter of a function that I wrote myself, like I do here on line 33, not just with built in functions.
Because I wanted to have the stopping function be able to recognize runs of three numbers at the end of a string (like 12333
) I needed to be able to know what the last character was when we’ve exhausted all the characters in the string, and to do that, I’d need to pass along the last character separately from the list. But I wouldn’t need it until the stopping function, so I wanted to be able to have it accept a default value. When I tried to put a default value only on the definition from lines 14-28, I got the following warning:
warning: def goodInteger/3 has multiple clauses and also declares default values. In such cases, the default values should be defined in a header. Instead of:
def foo(:first_clause, b \\ :default) do ... end
def foo(:second_clause, b) do ... end
one should write:
def foo(a, b \\ :default)
def foo(:first_clause, b) do ... end
def foo(:second_clause, b) do ... end
THAT was a useful message!
def goodInteger(list, count, char \\ "")
def goodInteger([], count, char) do
if count == 3 and char != "" do
"\"" <> String.duplicate(char, count) <> "\""
else
"-1"
end
end
def goodInteger([char | rest], count, _) do
next = List.first(rest)
if char != next do
# the character changed!
if count == 3 do
"\"" <> String.duplicate(char, count) <> "\""
else
# restart the count with new character
goodInteger(rest, 1)
end
else
# the character is the same
goodInteger(rest, count + 1, char)
end
end
def goodInteger(int) do
Integer.to_string(int)
|> String.graphemes
|> goodInteger(1)
end
View the entire Elixir script for this task on GitHub.
Task 2: Changing Keys
You are given an alphabetic string, $str
, as typed by user.
Write a script to find the number of times user had to change the key to type the given string. Changing key is defined as using a key different from the last used key. The shift
and caps lock
keys won’t be counted.
Example 1
Input: $str = 'pPeERrLl'
Output: 3
p -> P : 0 key change
P -> e : 1 key change
e -> E : 0 key change
E -> R : 1 key change
R -> r : 0 key change
r -> L : 1 key change
L -> l : 0 key change
Example 2
Input: $str = 'rRr'
Output: 0
Example 3
Input: $str = 'GoO'
Ouput: 1
Approach
Just like the last solution, this one is easily handled by breaking the string up into its constituent characters and handling them one by one.
Raku
This solution started off like the previous one. This time, though, to mix it up I used a while
loop to process the characters instead of a for
.
sub keyChanges($str) {
my @chars = $str.comb;
my $char = @chars.shift;
my $changes = 0;
while (my $next = @chars.shift) {
next if lc($char) eq lc($next);
$changes++;
$char = $next;
}
return $changes;
}
View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input: $str = 'pPeERrLl'
Output: 3
Example 2:
Input: $str = 'rRr'
Output: 0
Example 3:
Input: $str = 'GoO'
Output: 1
Perl
sub keyChanges($str) {
my @chars = split //, $str;
my $char = shift @chars;
my $changes = 0;
while (my $next = shift @chars) {
next if lc($char) eq lc($next);
$changes++;
$char = $next;
}
return $changes;
}
View the entire Perl script for this task on GitHub.
Python
def keyChanges(strVal):
chars = list(strVal)
char = chars.pop(0)
changes = 0
for next in chars:
if char.lower() != next.lower():
changes += 1
char = next
return changes
View the entire Python script for this task on GitHub.
Elixir
def keyChanges([], _, count), do: count
def keyChanges([char | rest], last, count) do
char = String.downcase(char)
last = String.downcase(last)
cond do
char == last -> keyChanges(rest, char, count)
true -> keyChanges(rest, char, count + 1)
end
end
def keyChanges(str) do
[first | rest] = String.graphemes(str)
keyChanges(rest, first, 0)
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-282/packy-anderson