This is my response to The Weekly Challenge #209.
[0] -decodes-to-> "a"
[1, 0] -> "b"
[1, 1] -> "c"
Write a script to print 1 if the last character is an “a” otherwise print 0.
Input: @bits = (1, 0, 0)
Output: 1
The given array bits can be decoded as 2-bits character (10) followed by
1-bit character (0).
Example 2:
Input: @bits = (1, 1, 1, 0)
Output: 0
Possible decode can be 2-bits character (11) followed by 2-bits character
(10) i.e. the last character is not 1-bit character.
#! /usr/bin/env raku
unit sub MAIN (*@bits where @bits.elems > 0 # [1]
&& all(@bits) eq any(0,1) # [1a]
&& @bits[*-1] == 0, # [1b]
:v($verbose));
my $string = ""; # [2]
while (@bits.elems) # [3]
{
my $first = @bits.shift; # [4]
if $first == 0 # [5]
{
$string ~= 'a'; # [5a]
}
elsif (@bits.elems) # [6]
{
my $second = @bits.shift; # [6a]
$string ~= $second == 0 ?? 'b' !! 'c'; # [6b]
}
else # [7]
{
$string ~= "ERROR"; # [7a]
}
}
say ":String: $string" if $verbose;
say + $string.ends-with('a'); # [8]
[1] A slurpy array with at least one element, the values must be either «0» or
«1» [1a] and the last value (ggiven with the [*-1]
index) must be
«0» [1b].
[2] The resulting string, which the challenge does not actually request, will end up here.
[3] As long as we have more values (bits) to parse.
[4] Get the next value (the first unparsed).
[5] Is it zero? In that case add an «a» to the string [5a].
[6] If not (i.e. it is «1»), and we have at least one more value, get that value [6a].
If the second value is zero, we get the letter «b» and if it is one, we get «c» [6b].
(~
is the string concatenation operator, and ~=
assigns
the new value back to the variable on the left, in the same way as e.g.
$a += 2
would for numeric values.)
[7] If the first value is one, and that one is the last one, we have an error.
Here I have just added the error message to the string. Note that the rule given
in [1b] makes this code unreachable. A die
is probably a better
choice here...
[8]
Print «1» if the last character in the string
is an «a». We use the aptly named ends-with
to look it up. The result is
a Boolean value, and we coerce that to a number (ie. True => 1, False => 0) with the
numeric coercion prefix +.
See
docs.raku.org/routine/ends-with
for more information about the ends-with
function.
See
docs.raku.org/routine/+ for
more information about the Numeric Coercion Prefix Operator +
.
Running it:
$ ./special-bit-characters 1 0 0
1
$ ./special-bit-characters 1 1 1 0
0
Looking good.
Verbose mode may help convince you that the result is correct for the right reasons:
$ ./special-bit-characters -v 1 0 0
:String: ba
1
$ ./special-bit-characters -v 1 1 1 0
:String: cb
0
Illegal input, i.e. the last value is not zero, is catched:
$ ./special-bit-characters 1 0 0 1
Usage:
./special-bit-characters [-v[=Any]] [<bits> ...]
Input: @accounts = [ ["A", "a1@a.com", "a2@a.com"],
["B", "b1@b.com"],
["A", "a3@a.com", "a1@a.com"] ]
]
Output: [ ["A", "a1@a.com", "a2@a.com", "a3@a.com"],
["B", "b1@b.com"] ]
Example 2:
Input: @accounts = [ ["A", "a1@a.com", "a2@a.com"],
["B", "b1@b.com"],
["A", "a3@a.com"],
["B", "b2@b.com", "b1@b.com"] ]
Output: [ ["A", "a1@a.com", "a2@a.com"],
["A", "a3@a.com"],
["B", "b1@b.com", "b2@b.com"] ]
This challenge can be quite difficult.
#! /usr/bin/env raku
unit sub MAIN (:v($verbose));
my @accounts1 = [ ["A", "a1@a.com", "a2@a.com"], # [1]
["B", "b1@b.com"],
["A", "a3@a.com", "a1@a.com"]
];
say "Example 1:"; # [1a]
merge-accounts(@accounts1); # [1a]
my @accounts2 = [ ["A", "a1@a.com", "a2@a.com"], # [2]
["B", "b1@b.com"],
["A", "a3@a.com"],
["B", "b2@b.com", "b1@b.com"]
];
say "\nExample2:"; # [2a]
merge-accounts(@accounts2); # [2a]
sub merge-accounts (@accounts) # [3]
{
my %accounts; # [4]
ACC:
for @accounts -> @account # [5]
{
my $key = @account.shift; # [6]
my @email = @account; # [6a]
if %accounts{$key} # [7]
{
for @(%accounts{$key}) -> @emails # [8]
{
if any(@emails) eq any(@email) # [9]
{
say ":Append $key emails: @email[] (to @emails[])" if $verbose;
@emails.append: @email; # [10]
next ACC; # [11]
}
}
}
say ":Add $key emails: @email[]" if $verbose;
%accounts{$key}.push: @email; # [12]
}
say "["; # [13]
for sort keys %accounts -> $key # [14]
{
for @(%accounts{$key}) -> @emails # [15]
{
say " [\"$key\", ", join(", ", @emails.unique.map({ "\"$_\""}) ), "],";
} # [16]
}
say "]"; # [13a]
}
You may want to run the program with verbose mode (as shown later on), and follow the verbose output lines of code to follow the program flow, whilst going through the following discussion.
[1] The first example.
[2] The second example.
[3] The procedure doing the merging and printing.
[4] The merged data will end up here.
[5] Iterate over the rows of accounts in the input.
[6] The first value is the account name (or key), and the rest of the values are email addresses; one or more [6a].
[7] Have we encountered an account with this name before?
[8] If so, iterate over the arrays of email addresses that we have saved.
[9] If the new account and the one in the loop has (at least) one common address,
[10] add the new account email addresses to this one, with
append
so that we add them to the current array.
See
docs.raku.org/routine/append
for more information about append
.
[11] Skip checking the rest of the rows in the loop [8] and
go to the next value in the input array [5]. This will skip the push
in [12].
See
docs.raku.org/routine/push
for more information about push
.
[12] No match if we get here, so add it as a new array. Note that push
ing
to a hash value turns the value into an array (and any existing scalar value already
there will silently go away (but not in REPL mode)).
Then the output, or presentation, of the resulting data structure:
[13] Print the outer brackets [13a].
[14] Iterate over the account names, in sorted order.
[15] Iterate over the arrays of email addresses for each one.
[16] Print the name and email addresses, quoted and comma
separated. Note the unique
to get rid of duplicates (the common
email addresses).
See
docs.raku.org/routine/unique
for more information about unique
.
Running it:
$ ./merge-account
Example 1:
[
["A", "a1@a.com", "a2@a.com", "a3@a.com"],
["B", "b1@b.com"],
]
Example2:
[
["A", "a1@a.com", "a2@a.com"],
["A", "a3@a.com"],
["B", "b1@b.com", "b2@b.com"],
]
The trailing comma on the last account line is redundant, but Raku (and Perl) does not mind. (We may not actually be required to actually print the result like this, just returning the data structure. But printing is certainly required to show what we got, so...
Running it with verbose mode:
$ ./merge-account -v
Example 1:
:Add A emails: a1@a.com a2@a.com
:Add B emails: b1@b.com
:Append A emails: a3@a.com a1@a.com (to a1@a.com a2@a.com)
[
["A", "a1@a.com", "a2@a.com", "a3@a.com"],
["B", "b1@b.com"],
]
Example2:
:Add A emails: a1@a.com a2@a.com
:Add B emails: b1@b.com
:Add A emails: a3@a.com
:Append B emails: b2@b.com b1@b.com (to b1@b.com)
[
["A", "a1@a.com", "a2@a.com"],
["A", "a3@a.com"],
["B", "b1@b.com", "b2@b.com"],
]
And that's it.