Digital Frequalizer
with Raku

by Arne Sommer

Digital Frequalizer with Raku

[214] Published 11. December 2022.

This is my response to The Weekly Challenge #194.

Challenge #194.1: Digital Clock

You are given time in the format hh:mm with one missing digit.

Write a script to find the highest digit between 0-9 that makes it valid time.

Example 1:
Input: $time = '?5:00'
Output: 1

Since 05:00 and 15:00 are valid time and no other digits can fit in the
  missing place.
Example 2:
Input: $time = '?3:00'
Output: 2
Example 3:
Input: $time = '1?:00'
Output: 9
Example 4:
Input: $time = '2?:00'
Output: 3
Example 5:
Input: $time = '12:?5'
Output: 5
Example 6:
Input: $time =  '12:5?'
Output: 9
File: digital-clock
#! /usr/bin/env raku

subset MissingTime where /^<[012\?]><[0..9\?]>\:<[0..5\?]><[0..9\?]>$/;      # [1]

unit sub MAIN (MissingTime $time where $time.comb.Bag<?> == 1);              # [2]

die "Illegal time"
  if $time.substr(0,1) eq "2" && $time.substr(1,1) ne any <0 1 2 3 ?>;       # [3]

given $time                                                                  # [4]
{
  when .substr(0,1) eq "?" && .substr(1,1) < 4 { say "2"; } # "?[0..3]:**"   # [5]
  when .substr(0,1) eq "?"                     { say "1"; } # "?[4..9]:**"   # [5a]
  when .substr(1,1) eq "?" && .substr(0,1) < 2 { say "9"; } # "[0..1]?:**"   # [6]
  when .substr(1,1) eq "?"                     { say "3"; } # "2?:**"        # [6a]
  when .substr(3,1) eq "?"                     { say "5"; } # "**:?*"        # [7]
  when .substr(4,1) eq "?"                     { say "9"; } # "**:*?"        # [8]
}

[1] A grammar is ideal here, but I have chosen the lazier route of using a custom type (with subset).

Feel free to read «lazier» as «crazier». I use a subset, two where clauses, a Bag, a substr(ing) and an any junction - just to avoid a grammar.

See docs.raku.org/language/typesystem#index-entry-subset-subset for more information about subset.

[2] The custom type allows up to four question marks, and thus we use a where clause that ensures that there is one and only one of them. We do this be turning the string into a single letters, and then into a Bag - which is a hash like structure where the characters are the keys, end the frequency is the value. Then we look up the question mark, and there should be only one.

See docs.raku.org/type/Bag for more information about the Bag type.

[3] The subset (in [1]) took care of the first digit (0, 1 or 2), but the second one cannot be higher than 3 if the first one is 2. This check ensures that - or a question mark.

[4] Using given/when, which is Raku speak for «switch», gives compact code.

[5] The first character is a question mark. The highest value is either 2 or 1 [5a], depending on the second digit.

[6] The second character is a question mark. The highest value is either 9 or 3 [6a], depending on the first digit.

[7] The third digit has 5 as the highest value.

[8] The fourth digit has 9 as the highest value.

Running it:

$ ./digital-clock ?5:00
1

$ ./digital-clock ?3:00
2

$ ./digital-clock 1?:00
9

$ ./digital-clock 2?:00
3

$ ./digital-clock 12:?5
5

$ ./digital-clock 12:5?
9

Looking good.

Challenge #194.2: Frequency Equalizer

You are given a string made of alphabetic characters only, a-z.

Write a script to determine whether removing only one character can make the frequency of the remaining characters the same.

Example 1:
Input: $s = 'abbc'
Output: 1 since removing one alphabet 'b' will give us 'abc' where each
  alphabet frequency is the same.
Example 2:
Input: $s = 'xyzyyxz'
Output: 1 since removing 'y' will give us 'xzyyxz'.
Example 2:
Input: $s = 'xzxz'
Output: 0 since removing any one alphabet would not give us string with
  same frequency alphabet.
File: frequency-equalizer
#! /usr/bin/env raku

subset az where /^<[a..z]>+$/;         # [1a]

unit sub MAIN (az $s, :v(:$verbose));  # [1]

my @freq = $s.comb.Bag.values.sort;    # [2]

say ": Frequency sorted: @freq[]" if $verbose;

@freq.push(@freq.pop - 1);             # [3]

say ": Frequency lowered: @freq[]" if $verbose;

say ( [==] @freq ) ?? 1 !! 0;          # [4]

[1] Ensure that the input only conains klower case letters (a..z), using a custom type (set up in [1a]).

[2] Get the frequency of the used letters. The Bag gives a hash like structure (as explained in the first part of this challenge), and we apply .values to get just the values - or the frequencies. Then we sort the list, giving us the highest value at the end.

[3] Remove the highest value (with pop that takes the last value in the list), subtract one, and push the new value back in the list og frequencies.

[4] Now all the frequencies should be the same. We use [==] to check this. The Reduction Metaoperator [] inserts the given operator (the numeric eqality operator ==) between each value in the list and collapses the whole shabang to a Boolean value. Finally we print «1» if they are equal, and «0» otherwise.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

Running it:

$ ./frequency-equalizer abbc
1

$ ./frequency-equalizer xyzyyxz
1

$ ./frequency-equalizer xzxz
0

Looking good.

With verbode mode:

$ ./frequency-equalizer -v abbc
: Frequency sorted: 1 1 2
: Frequency lowered: 1 1 1
1

$ ./frequency-equalizer -v xyzyyxz
: Frequency sorted: 2 2 3
: Frequency lowered: 2 2 2
1

$ ./frequency-equalizer -v xzxz
: Frequency sorted: 2 2
: Frequency lowered: 2 1
0

And that's it.