Perl Weekly Challenge: Ch-ch-ch-changes!

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