Broken Digits
with Raku

by Arne Sommer

Broken Digits with Raku

[295] Published 26. June 2024.

This is my response to The Weekly Challenge #275.

Challenge #275.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
File: broken-keys
#! /usr/bin/env raku

unit sub MAIN ($sentence where $sentence.chars > 0,             # [1]
               *@keys where @keys.elems > 0,                    # [2]
               :v(:$verbose));

my @words = $sentence.words;                                    # [3]

say ": Words: { @words.map({ "'$_'"}).join(", ")}" if $verbose;

my @fully = @words.map({ $_.contains(any(@keys)) ?? 0 !! 1 });  # [4]

say ": Fully: { @fully.join(", ") }" if $verbose;

say @fully.sum;                                                 # [5]

[1] Ensure at least one character in the sentence.

[2] Use a slurpy array (the * prefix) for the keys, and ensure that we get at least one of them.

[3] Get the individual words (with words), or rather what Raku considers words.

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

[4] Use map to convert each word into 1 if the word is fully, and 0 if not. We use contains to decide this, combined with an any junction on all the keys.

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

[5] Add up the ones and zeroes and print the result.

Running it:

$ ./broken-keys "Perl Weekly Challenge" l a
0

$ ./broken-keys "Perl and Raku" a
1

$ ./broken-keys "Well done Team PWC" l o
2

$ ./broken-keys "The joys of polyglottism" T
3

Looking good, except for the last one. It seems that the matching should be case insenstive.

Here is verbose mode, in case you want it, before we tackle the case of the case:

$ ./broken-keys -v "Perl Weekly Challenge" l a
: Words: 'Perl', 'Weekly', 'Challenge'
: Fully: 0, 0, 0
0

$ ./broken-keys -v "Perl and Raku" a
: Words: 'Perl', 'and', 'Raku'
: Fully: 1, 0, 0
1

$ ./broken-keys -v "Well done Team PWC" l o
: Words: 'Well', 'done', 'Team', 'PWC'
: Fully: 0, 0, 1, 1
2

$ ./broken-keys -v "The joys of polyglottism" T
: Words: 'The', 'joys', 'of', 'polyglottism'
: Fully: 0, 1, 1, 1
3

The modified program:

File: broken-keys-lc
#! /usr/bin/env raku

unit sub MAIN ($sentence where $sentence.chars > 0,
               *@keys where @keys.elems > 0,
               :v(:$verbose));

my @words   = $sentence.words;
my @lc_keys = @keys>>.lc;                                             # [1]

say ": Words: { @words.map({ "'$_'"}).join(", ")}" if $verbose;

my @fully = @words.map({ $_.lc.contains(any(@lc_keys)) ?? 0 !! 1 });  # [2]

say ": Fully: { @fully.join(", ") }" if $verbose;

say @fully.sum;

[1] Convert the entire array of words to lowercase with >>.lc.

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

[2] Convert each word to lowercase before the comparison.

Running it gives the correct result for all four examples, shown here with verbose mode:

$ ./broken-keys-lc -v "Perl Weekly Challenge" l a
: Words: 'Perl', 'Weekly', 'Challenge'
: Fully: 0, 0, 0
0

$ ./broken-keys-lc -v "Perl and Raku" a
: Words: 'Perl', 'and', 'Raku'
: Fully: 1, 0, 0
1

$ ./broken-keys-lc -v "Well done Team PWC" l o
: Words: 'Well', 'done', 'Team', 'PWC'
: Fully: 0, 0, 1, 1
2
    
$ ./broken-keys-lc -v "The joys of polyglottism" T
: Words: 'The', 'joys', 'of', 'polyglottism'
: Fully: 0, 1, 1, 0
2

Challenge #275.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'

The challenge does not say what to do if the replacement character is not a letter, so I have chosen to ignore that problem.

File: replace-digits
#! /usr/bin/env raku

unit sub MAIN ($str where $str ~~ /^<[a..z]> <[a..z 0..9]>*$/);    # [1]

$str.comb.map( *.&do-magic ).join.say;                             # [2]

sub do-magic ($letter)                                             # [3]
{
  state $prev = "";                                                # [4]

  return ($prev.ord + $letter).chr if $letter eq any('0' .. '9');  # [5]

  $prev = $letter;                                                 # [6]

  return $letter;                                                  # [7]
}

[1] EWnsure that we get one letter (lower case only, as in the examples), followed by zero or more letters (also lower case only) or digits.

[2] Split the string into individual characters (with comb) apply the custom procedure to each value (with map), join the result together to a new string, and print the result.

[3] The procedure, taking one character as input.

[4] The previous character, as a state variable so that we keep the value between calls.

See docs.raku.org/syntax/state for more information about the variable declarator state.

[5] If we get a digit, return a modified version of the previous character (that we have saved in the state variable).

[6] We have a letter if we get here. Save it for later.

[7] Return the letter unchanged.

Running it:

$ ./replace-digits a1c1e1
abcdef

$ ./replace-digits a1b2c3d4
abbdcfdh

$ ./replace-digits b2b
bdb

$ ./replace-digits a16z
abgz

Looking good.

And that's it.