This is my response to The Weekly Challenge #384.
Input: $num = 42, $base = 2
Output: 101010
Example 2:
Input: $num = 15642094, $base = 16
Output: EEADEE
Example 3:
Input: $num = 493, $base = 8
Output: 755
Example 4:
Input: $num = 2228519, $base = 36
Output: 1BRJB
Base 36 uses numbers 0-9 and letters A-Z.
Example 5:
Input: $num = 123456789, $base = 64
Output: 7MyqL
Base 64 (using 0-9, A-Z, a-z, and extra symbols like + and /)
#! /usr/bin/env raku
multi sub MAIN (Int :n(:$num),
Int :b(:$base) where $base == 1,
:v(:$verbose))
{
say ( '0' x $num );
}
multi sub MAIN (Int :n(:$num),
Int :b(:$base) where 2 <= $base <= 16,
:v(:$verbose))
{
say $num.base($base);
}
multi sub MAIN (Int :n(:$num) is copy,
Int :b(:$base) where 17 <= $base <= 64,
:v(:$verbose))
{
my $result = "";
while $num
{
my $digit = $num % $base;
my $new = $num div $base;
my $in-base = do given $digit
{
when * < 10 { $digit }
when * < 36 { ($digit - 10 + 'A'.ord).chr }
when * < 62 { ($digit - 36 + 'a'.ord).chr }
when * == 62 { "+" }
when * == 63 { "/" }
}
say ": $num / $base = $new + $digit -> $in-base"
if $verbose;
$result ~= $in-base;
$num = $new;
}
say $result.flip;
}
I have chosen multiple dispatch (with multi) for this one,
as the bases can be grouped in three.
[3] Base 1 is easy, but not really a positional number system, so perhaps should be considered illegal by the program.
[7] Print as many zeroes as the value of the base. The actual symbol is irrelevant, but zero fits in with the general pattern; base 2 (0,1), base 3 (0,1,2), and so on. Note that the number zero is represented as en empty string, which really makes this a peculiar number system...
[10] Base 2 to 16 are easy, as we can use the
See docs.raku.org/routine/base for more information about base.
[16] Base 17 to 64 must be dealt with manually. (I have not checked if there is a Raku module for this.)
[21] The resulting string will end up here, backwards.
[23] As long as we have not reduced the input value to zero.
[25] Get the new digit (in decimal, so may actually be several digits) with
the modulo operator %.
[26] Get the new input variable.
[28]
Convert the digit to the given base with given/when.
The do part collapses the given block (pun intended) so that we can
assign it like this.
See docs.raku.org/syntax/default when for more information about given/when/default.
See docs.raku.org/routine/do for more information about do.
[39] Add the new digit to the result.
[41] Update the input variable.
[44] Print he result, but reversed (with flip), as the
loop gave us the digits in wrong order.
See docs.raku.org/routine/flip for more information about flip.
Running it:
$ ./base-n -n=42 -b=2
101010
$ ./base-n -n=15642094 -b=16
EEADEE
$ ./base-n -n=493 -b=8
755
$ ./base-n -n=2228519 -b=36
1BRJB
$ ./base-n -n=123456789 -b=64
7MyqL
Looking good.
With verbose mode (which is only implemented for bases 17 to 64):
$ ./base-n -v -n=42 -b=2
101010
$ ./base-n -v -n=15642094 -b=16
EEADEE
$ ./base-n -v -n=493 -b=8
755
$ ./base-n -v -n=2228519 -b=36
: 2228519 / 36 = 61903 + 11 -> B
: 61903 / 36 = 1719 + 19 -> J
: 1719 / 36 = 47 + 27 -> R
: 47 / 36 = 1 + 11 -> B
: 1 / 36 = 0 + 1 -> 1
1BRJB
$ ./base-n -v -n=123456789 -b=64
: 123456789 / 64 = 1929012 + 21 -> L
: 1929012 / 64 = 30140 + 52 -> q
: 30140 / 64 = 470 + 60 -> y
: 470 / 64 = 7 + 22 -> M
: 7 / 64 = 0 + 7 -> 7
7MyqL
Input: $binary = "0101"
Output: ("01", "10")
Example 2:
Input: $binary = "000111"
Output: ("000111", "0011", "01")
Example 3:
Input: $binary = "000011"
Output: ("0011", "01")
Example 4:
Input: $binary = "10011100"
Output: ("10", "0011", "01", "1100")
Example 5:
Input: $binary = "00000"
Output: ()
#! /usr/bin/env raku
subset BINARY where /^ <[01]>+ $ /;
unit sub MAIN (BINARY $binary, :v(:$verbose));
my @ok;
for 0 .. $binary.chars - 2 -> $i
{
for $i + 1 .. $binary.chars - 1 -> $j
{
my $candidate = $binary.substr($i, $j - $i +1);
next unless $candidate.chars %% 2;
next unless $candidate.comb.sum == $candidate.chars / 2;
next unless $candidate ~~ /^ (0+ 1+ | 1+ 0+) $/;
say ":$binary" ~ "[$i .. $j] -> $candidate" if $verbose;
@ok.push: $candidate;
}
}
say "({ @ok.unique.map( '"' ~ * ~ '"' ).join(", ") })";
[3] A custom type with subset, to be used in [5].
See docs.raku.org/language/typesystem#subset for more information about subset.
[5] Ensure a binary number, with at least one digit.
[7] The special binary substrings will end up here.
[9] Iterate over all possible starting indices,
[11] and ending indices. Note that the minimum substring length is 2.
[13] Get the actual substring.
[15] Skip substrings that does not have an even length.
[16] Skip substrings where the number of 0s and 1s are the
same.
[17] Skip substring where the digits are not sorted in two groups.
[21] We have a candidate, add it to the result.
[25] Get rid of duplicates (the "(distinct)" clause in the challenge),
if any, with unique and pretty print the result.
Running it:
$ ./special-binary-substring 0101
("01", "10", "01")
$ ./special-binary-substring 000111
("000111", "0011", "01")
$ ./special-binary-substring 000011
("0011", "01")
$ ./special-binary-substring 10011100
("10", "0011", "01", "1100")
$ ./special-binary-substring -v 00000
()
Looking good.
With verbose mode:
$ ./special-binary-substring -v 0101
:0101[0 .. 1] -> 01
:0101[1 .. 2] -> 10
:0101[2 .. 3] -> 01
("01", "10", "01")
)$ ./special-binary-substring -v 000111
:000111[0 .. 5] -> 000111
:000111[1 .. 4] -> 0011
:000111[2 .. 3] -> 01
("000111", "0011", "01")
$ ./special-binary-substring -v 000011
:000011[2 .. 5] -> 0011
:000011[3 .. 4] -> 01
("0011", "01")
$ ./special-binary-substring -v 10011100
:10011100[0 .. 1] -> 10
:10011100[1 .. 4] -> 0011
:10011100[2 .. 3] -> 01
:10011100[4 .. 7] -> 1100
:10011100[5 .. 6] -> 10
("10", "0011", "01", "1100")
$ ./special-binary-substring -v 00000
()
The fourth example has a duplicate value (10), as shown above, but
the unique method (in [25]) got rid of it for us.
And that's it.