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.