This is my response to The Weekly Challenge #386.
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...
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.