Reverse Lanoitar
with Raku

by Arne Sommer

Reverse Lanoitar with Raku

[411] Published 16. August 2026.

This is my response to The Weekly Challenge #386.

#386.1 Reverse Base You are given a string representing a number, and an integer specifying the base of that representation.

Write a function to convert this string to an integer. (For bases greater than 10, use characters A-Z, a-z, + and / in that order.)

Example 1:
Input: $num = "101010", $base = 2
Output: 42
Example 2:
Input: $num = "EEADEE", $base = 16
Output: 15642094
Example 3:
Input: $num = "755", $base = 8
Output: 493
Example 4:
Input: $num = "1BRJB", $base = 36
Output: 2228519
Example 5:
Input: $num = "7MyqL", $base = 64
Output: 123456789

This is the opposite (or reverse) of what we did in #384.1 Base N, two weeks ago.

File: reverse-base-error
#! /usr/bin/env raku

sub digit-value ($char)
{
  given $char
  {
    when '0' .. '9' { $char.Int }
    when 'A' .. 'Z' { $char.ord - 'A'.ord + 10 }
    when 'a' .. 'z' { $char.ord - 'a'.ord + 36 }
    when '+'        { 62 }
    when '/'        { 63 }
  }
}

multi sub MAIN (Str :n(:$num),
                Int :b(:$base) where 1 == $base,
                    :v(:$verbose))
{
  say $num.chars;
}

multi sub MAIN (Str :n(:$num),
                Int :b(:$base) where 2 <= $base <= 64,
		    :v(:$verbose))
{
  my $result = 0;

  for $num.comb -> $char
  {
    my $new = $result * $base;
    $result = $new + digit-value($char);

    say ": adding $char to $new -> $result" if $verbose;
  }

  say $result;
}

[3] Procedure returning the decimal value of the «in base» digit.

[5] Use given/when to choose correct way to compute the value.

See docs.raku.org/syntax/default when for more information about given/when/default.

[15] In case of «base 1», just return the length of the string.

[22] This one is when we specify a real base.

[26] The result will en up here.

[28] Iterate over the digits, from the left.

[30] Multiply the former result with the base,

[31] then add the decimal value of the current digit.

We have a positional number system (base 1 is not, but we have handled that one separately), so when we move a digit to the left we increase its value by multiplying it with the base. A decimal example: Start with «9». The next digfit is «2». We multiply the first one (9) with the base (19), and add the new digit (2). That gives us «92».

Nobody in their right (or left) mind would treat base 10 numbers in that way, but this works for other bases as well, as seen here, when we are converting the value to decimal.

Running it:

$ ./reverse-base-error -n=101010 -b=2
42

$ ./reverse-base-error -n=EEADEE -b=16
15642094

./reverse-base-error -n=755 -b=8
493

$ ./reverse-base-error -n=1BRJB -b=36
2228519

$ ./reverse-base-error -n=7MyqL -b=64
123456789

Looking good.

With verbose mode:

$ ./reverse-base-error -n=101010 -b=2 -v
: adding 1 (1) to 0 -> 1
: adding 0 (0) to 2 -> 2
: adding 1 (1) to 4 -> 5
: adding 0 (0) to 10 -> 10
: adding 1 (1) to 20 -> 21
: adding 0 (0) to 42 -> 42
42

$ ./reverse-base-error -n=EEADEE -b=16 -v
: adding 14 (E) to 0 -> 14
: adding 14 (E) to 224 -> 238
: adding 10 (A) to 3808 -> 3818
: adding 13 (D) to 61088 -> 61101
: adding 14 (E) to 977616 -> 977630
: adding 14 (E) to 15642080 -> 15642094
15642094

$ ./reverse-base-error -n=755 -b=8 -v
: adding 7 (7) to 0 -> 7
: adding 5 (5) to 56 -> 61
: adding 5 (5) to 488 -> 493
493

$ ./reverse-base-error -n=1BRJB -b=36 -v
: adding 1 (1) to 0 -> 1
: adding 11 (B) to 36 -> 47
: adding 27 (R) to 1692 -> 1719
: adding 19 (J) to 61884 -> 61903
: adding 11 (B) to 2228508 -> 2228519
2228519

$ ./reverse-base-error -n=7MyqL -b=64 -v
: adding 7 (7) to 0 -> 7
: adding 22 (M) to 448 -> 470
: adding 60 (y) to 30080 -> 30140
: adding 52 (q) to 1928960 -> 1929012
: adding 21 (L) to 123456768 -> 123456789
123456789

You may have spotted a problem with this program (and the name should have given a hint). There is no check that the digits are legal in the given base, if the base is lower than 64.

$ ./reverse-base-error -n=7MyqL -b=10 -v
: adding 7 (7) to 0 -> 7
: adding 22 (M) to 70 -> 92
: adding 60 (y) to 920 -> 980
: adding 52 (q) to 9800 -> 9852
: adding 21 (L) to 98520 -> 98541
98541

Let us fix that.

File: reverse-base
#! /usr/bin/env raku

sub digit-value ($char)
{
  given $char
  {
    when '0' .. '9' { $char.Int }
    when 'A' .. 'Z' { $char.ord - 'A'.ord + 10 }
    when 'a' .. 'z' { $char.ord - 'a'.ord + 36 }
    when '+'        { 62 }
    when '/'        { 63 }
  }
}

multi sub MAIN (Str :n(:$num),
                Int :b(:$base) where 1 == $base,
                    :v(:$verbose))
{
  say $num.chars;
}

multi sub MAIN (Str :n(:$num),
                Int :b(:$base) where 2 <= $base <= 64,
		    :v(:$verbose))
{
  my $alphabet = ('0' .. '9', 'A' .. 'Z', 'a' .. 'z', '+', '/')
		   >>.join.join.substr(0, $base);

  my %alphabet = $alphabet.comb.Set;

  my $result   = 0;

  say ": Alphabet: $alphabet" if $verbose;

  for $num.comb -> $char
  {
    die "Illegal char $char in a base $base number"
      unless %alphabet{$char};
  
    my $new = $result * $base;
    my $add = digit-value($char);
    $result = $new + $add;

    say ": adding $add ($char) to $new -> $result" if $verbose;
  }

  say $result;
}

[26] Get the legal characters for the given base, as a string.

[29] Turn it into a hash, for easy lookup.

[37] Abort on illegal input.

Running it:

$ ./reverse-base -n=1BRJB -b=3 -v
: Alphabet: 012
: adding 1 (1) to 0 -> 1
Illegal char B in a base 3 number
  in sub MAIN at ./reverse-base line 31
  in block >unit< at ./reverse-base line 3

Another, and shorter, way could have been adding a check on $add beeing lower than $base right after [41]. But this way is more fun...

#386.2 Rational Numbers You are given two strings representing non-negative rational numbers.

Write a script to return true if the two given rational numbers are same otherwise false.

Example 1:
Input: $rat1 = "0.(12)"
       $rat2 = "0.(121)"
Output: false

Expansion of "0.(12)"  = 0.12 12 12 12
Expansion of "0.(121)" = 0.121 121 121
Example 2:
Input: $rat1 = "0.1(23)"
       $rat2 = "0.12(32)"
Output: true

Expansion of "0.1(23)"  = 0.1 23 23 23
Expansion of "0.12(32)" = 0.12 32 32 32
Example 3:
Input: $rat1 = "0.1(234)"
       $rat2 = "0.12(342)"
Output: true

Expansion of "0.1(234)"  = 0.1 234 234 234
Expansion of "0.12(342)" = 0.12 342 342 342
Example 4:
Input: $rat1 = "12.99(99)"
       $rat2 = "13."
Output: true
Example 5:
Input: $rat1 = "0.(123)"
       $rat2 = "0.1(231)"
Output: true

Nothing much to go on here, but Wikipedia comes to the rescue. The program is based on the "shortcut" formula.

File: rational-numbers
#! /usr/bin/env raku

unit sub MAIN (Str $rat1, Str $rat2, :v(:$verbose));

my ($n1, $d1, $n2, $d2) = flat ($rat1, $rat2)>>.&parse-rat;

if $verbose
{
  say ": $rat1 -> $n1 - $d1";
  say ": $rat2 -> $n2 - $d2";
}

say $n1 * $d2 == $n2 * $d1;

sub parse-rat(Str $s)
{
  my $m = $s ~~ /^ (\d*) \. (\d*) \(? (\d*) \)? $/;

  my ($int, $nonrep, $rep) = ~$m[0], ~$m[1], ~$m[2];

  my ($b, $c) = ($nonrep, $rep)>>.chars;

  return +"$int$nonrep", 10 ** $b unless $c;

  my $full = +"$int$nonrep$rep";

  return $full - +"$int$nonrep", (10 ** $c - 1) * 10 ** $b;
}

[3] The two rational numbers as strings.

[5] Use the >>.& hyperoperator to call the helper sub on each string, then flat to flatten the resulting pairs into four scalar variables: numerators and denominators.

[13] Compare the two fractions via cross-multiplication to avoid floating-point issues, and report the result.

[15] The helper sub that converts a decimal string (with an optional repeating part) to a numerator/denominator pair.

[17] Use a regex to extract the three parts: integer part, non-repeating fractional part, and the repeating part (inside parentheses).

[19] Extract the three captures and stringify them (the match objects) vith ~.

[21] Get the length of each part.

[23] No repeating part? If so, simply return the combined integer+fraction as numerator, with denominator 10b.

[25] With a repeating part, compute the combined integer+nonrepeating+repeating as a number.

[27] Apply the "shortcut" formula.

Running it:

$ ./rational-numbers "0.(12)" "0.(121)"
False

$ ./rational-numbers "0.1(23)" "0.12(32)"
True

$ ./rational-numbers "0.1(234)" "0.12(342)"
True

$ ./rational-numbers "12.99(99)" "13."
True

$ ./rational-numbers "0.(123)" "0.1(231)"
True

Looking good.

With verbose mode:

$ ./rational-numbers -v "0.(12)" "0.(121)"
: 0.(12) -> 12 - 99
: 0.(121) -> 121 - 999
False

$ ./rational-numbers -v "0.1(23)" "0.12(32)"
: 0.1(23) -> 122 - 990
: 0.12(32) -> 1220 - 9900
True

$ ./rational-numbers -v "0.1(234)" "0.12(342)"
: 0.1(234) -> 1233 - 9990
: 0.12(342) -> 12330 - 99900
True

$ ./rational-numbers -v "12.99(99)" "13."
: 12.99(99) -> 128700 - 9900
: 13. -> 13 - 1
True

$ ./rational-numbers -v "0.(123)" "0.1(231)"
: 0.(123) -> 123 - 999
: 0.1(231) -> 1230 - 9990
True

And that's it.