Changingly Good
with Raku

by Arne Sommer

Changingly Good with Raku

[303] Published 16. August 2024.

This is my response to The Weekly Challenge #282.

Challenge #282.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"

Off we go, without careful reading of the challenge...

File: good-integer-regexp
#! /usr/bin/env raku

unit sub MAIN (UInt $int where $int > 99);         # [1]

say $int ~~ /(.) {} :my $c=$0; <?after $c ** 3> /  # [2]
  ?? $0.Str x 3                                    # [2a]
  !! -1;                                           # [2b]

[1] An unsigned integer, with a value of 100 or above.

[2] This «three identical characters in a row» regexp is reused (slightly modified) from my recent The Nine Billion Names of God with Raku article. We capture only one of them (the characters, with the parens), so have to print three of that one with the string repetition operator x). Note the stringification courtesy of Str, as working with a match object does not really work out here [2a]. Or -1 on failure.

See docs.raku.org/routine/x for more information about x.

Running it:

$ ./good-integer-regexp 12344456
444

$ ./good-integer-regexp 1233334
333

$ ./good-integer-regexp 10020003
000

Oops. The second one is wrong. The regexp matches three or more, and not exactly three as prescribed by the challenge.

A second try, and a considerable longer program to boot.

File: good-integer
#! /usr/bin/env raku

unit sub MAIN (UInt $int where $int > 99, :v(:$verbose));

my $matching := gather          # [1]
{
  my @digits  = $int.comb;      # [2]
  my $current = @digits.shift;  # [3]
  my $count   = 1;              # [4]

  while @digits                 # [5]
  {
    my $next = @digits.shift;   # [6]

    if $current eq $next        # [7]
    {
      $count++;                 # [7a]
    }
    else                        # [8]
    {
      take $current x $count;   # [8a]
      $current = $next;         # [8b]
      $count = 1;               # [8c]
    }
  }

  take $current if $current;    # [9]
}

for $matching -> $candidate     # [10]
{
  say ":Considering candidate '$candidate'" if $verbose;
  if $candidate.chars == 3      # [11]
  {
    say $candidate;             # [11a]
    exit;                       # [11b]
  }
}
    
say -1;                         # [12]

[1] Using gather (here) and take (in [8a] and [9]) to set up a partial string feed works pretty well.

[2] Turn the input into an array of individual digits.

[3] Get the first digit.

[4] The size of the partial string starts out at 1.

[5] As long as we have additional digits to process.

[6] Get the next one.

[7] Identical to the previous one? If so, count it.

[8] If not, return (with take) the old value [8a], save the new value [8b] and reset the counter [8c].

[9] Return the last value on loop termination. The test is actually not really needed.

[10] Iterate over the partial strings.

[11] Does it have the required length? If so, print it [11a] and exit [11b].

[12] We have failed to find a match if we reach this part. Say so.

Running it:

$ ./good-integer 12344456
444

$ ./good-integer 1233334
-1

$ ./good-integer 10020003
000

Looking good.

With verbose mode:

$ ./good-integer -v 12344456
:Considering candidate '1'
:Considering candidate '2'
:Considering candidate '3'
:Considering candidate '444'
444

$ ./good-integer -v 1233334
:Considering candidate '1'
:Considering candidate '2'
:Considering candidate '3333'
:Considering candidate '4'
-1

$ ./good-integer -v 10020003
:Considering candidate '1'
:Considering candidate '00'
:Considering candidate '2'
:Considering candidate '000'
000

One can argue that «000» is not an integer. It is easy to amend the program to support that notion, but I will refrain from doing so.

Challenge #282.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'
Ouput: 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'
Ouput: 0
Example 3:
Input: $str = 'GoO'
Ouput: 1

File: changing-keys
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^<[a..z A..Z]>+$/,  # [1]
               :v(:$verbose));

my @letters = $str.comb;                               # [2]
my $first   = @letters.shift;
my $count   = 0;

while @letters
{
  my $second = @letters.shift;

  my $change = $first.lc ne $second.lc;                # [3]

  say ": $first -> $second : { +$change } key change" if $verbose;

  $count++ if $change;                                # [4]

  $first = $second;                                   # [5]
}

say $count;                                           # [6]

[1] Ensure english letters only, lower- and uppercase.

[2] This and the loop is almost a copy of the one in the first half of the challenge.

[3] Compare the lowercase versions (lc) of the strings.

See docs.raku.org/routine/lc for more information about lc.

[4] Increase the counter if the letters differ.

[5] Set up for the next iteration.

[6] Print the result.

Running it:

$ ./changing-keys pPeERrLl
3

$ ./changing-keys rRr
0

$ ./changing-keys GoO
1

Looking good.

With verbose mode:

$ ./changing-keys -v pPeERrLl
: 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
3

$ ./changing-keys -v rRr
: r -> R : 0 key change
: R -> r : 0 key change
0

$ ./changing-keys -v GoO
: G -> o : 1 key change
: o -> O : 0 key change
1

And that's it.