This is my response to the Perl Weekly Challenge #070.
$S
of size $N
.
$C
and offset $O
such that $C >= 1
,
$O >= 1
, $C <= $O
and $C + $O <= $N
.
Input:
$S = 'perlandraku'
$C = 3
$O = 4
Character Swapping:
swap 1: e <=> n = pnrlaedraku
swap 2: r <=> d = pndlaerraku
swap 3: l <=> r = pndraerlaku
Output:
pndraerlaku
It is easier to change elements in an array, than characters inside a string, so I'll do just that.
File: charswap-mod
#! /usr/bin/env raku
subset PosInt of Int where * >= 1; # [1]
unit sub MAIN (:$S = 'perlandraku', # [2]
PosInt :$O = 4,
PosInt :$C where $C < $O && $C + $O <= $S.chars = 3, # [3]
:$v, :$verbose = $v); # [4]
my @S = $S.comb; # [5]
my $N = $S.chars; # [6]
for 1 .. $C -> $index # [7]
{
say "swap $index: { @S[$index % $N] } <-> { @S[ ($index + $O) % $N ] } \
== { @S.join }" if $verbose;
swap(@S[$index % $N], @S[ ($index + $O) % $N ]); # [8]
}
say @S.join; # [9]
sub swap ($a is rw, $b is rw) # [10]
{
($a, $b) = ($b, $a);
}
[1] Both $O
and $C
are required to be positive
integers (i.e. larger than or equal to zero). A custom type makes it easier
to see what is going on, rather than adding another where clause on them.
Especially line [4].
[2] I have chosen named arguments, so that we can let if fall back to default values for any argument.
[3] Quite a where
clause. Note the default value at the end,
which is not part of that clause.
[4] Verbose mode (--verbose or -verbose) and the shortcut (--v or -v).
[5] The individual letters in an array, as it is easier to handle array elements than single characters in a string (substrings).
[6] The length of the string. We needed that value in [3], but the variable is not available there.
[7] Iterate over the indeces of the letters to swap, starting with 1. This means that the first letter (at index 0) will not be swapped, ever.
[8] Swap the two letters, using a procedure (see [10]).
[9] Print the result after the swappings. We have an array, so
[10] Procedure arguments are read only by default, so we cannot change them.
is copy
would give us a writeable copy, but the values
outside of the procedure would not be effected. Using is rw
solves this, as we can write to the original variable. This requires writeable
variables, so a call like swap("p", "e")
will fail (and terminate
the program).
Running it:
$ /charswap-mod
pndraerlaku
$ .charswap-mod -v
swap 1: e <-> n == perlandraku
swap 2: r <-> d == pnrlaedraku
swap 3: l <-> r == pndlaerraku
pndraerlaku
$ ./charswap-mod -S="This is not the end" -O=9 -C=7
Tt the enohis is nd
$ ./charswap-mod -v -S="This is not the end" -O=9 -C=7
swap 1: h <-> t == This is not the end
swap 2: i <-> == Ttis is noh the end
swap 3: s <-> t == Tt s is nohithe end
swap 4: <-> h == Tt t is nohishe end
swap 5: i <-> e == Tt this nohis e end
swap 6: s <-> == Tt thes nohis i end
swap 7: <-> e == Tt the nohis isend
Tt the enohis is nd
The % $N
part (modulo N) can be removed,
as it will never be triggered because of the $C + $O <= $S.chars
clause in [4].
And we can remove $N
as well, as it is not used.
#! /usr/bin/env raku
subset PosInt of Int where * >= 1;
unit sub MAIN (:$S = 'perlandraku',
PosInt :$O = 4,
PosInt :$C where $C < $O && $C + $O <= $S.chars = 3,
:$v, :$verbose = $v);
my @S = $S.comb;
for 1 .. $C -> $index
{
say "swap $index: { @S[$index] } <-> { @S[$index + $O] } == { @S.join }"
if $verbose;
swap(@S[$index], @S[$index + $O]);
}
say @S.join;
sub swap ($a is rw, $b is rw)
{
($a, $b) = ($b, $a);
}
Running it gives the same result:
$ ./charswap-sans-mod
pndraerlaku
$ ./charswap-sans-mod -S="This is not the end" -O=9 -C=7
Tt the enohis is nd
And finally, a version working on substrings:
File: charswap
#! /usr/bin/env raku
subset PosInt of Int where * >= 1;
unit sub MAIN (:$S is copy = 'perlandraku',
PosInt :$O = 4,
PosInt :$C where $C < $O && $C + $O <= $S.chars = 3,
:$v, :$verbose = $v);
for 1 .. $C -> $index
{
say "swap $index: { $S.substr($index, 1) } <-> \
{ $S.substr($index + $O, 1) } == $S" if $verbose;
($S.substr-rw($index,1), $S.substr-rw($index + $O,1) ) = # [1]
($S.substr($index + $O, 1), $S.substr($index, 1)); # [1a]
}
say $S;
[1] The substr
method (in [1a]) returns a copy, so we
have to use substr-rw
(in [1]) instead as we want to
change the values.
See
docs.raku.org/routine/substr and
docs.raku.org/routine/substr-rw
for more information about the substr
and substr-rw
.
The moral, such as it is, is that it is actually easier to work with substrings (than arrays). So my gut feeling didn't survive the reality check. Oh well.
#! /usr/bin/env perl
use strict;
use feature 'say';
my $verbose = 0;
if (@ARGV && $ARGV[0] eq "--verbose")
{
$verbose = 1;
shift(@ARGV);
}
my $S = shift(@ARGV) // 'perlandraku';
my $N = length $S;
my $O = shift(@ARGV) // 4;
my $C = shift(@ARGV) // 3;
die '$O: Integer >= 1 only' unless int $O == $O;
die '$C: Integer >= 1 only' unless int $C == $C;
die '$C: Must be < $O' unless $C < $O;
die '$C + $O: Must be <= $N' unless $C + $O <= $N;
for my $index (1 .. $C)
{
say "swap $index: " . substr($S, $index, 1) . " <-> "
. substr($S, $index + $O, 1) . " == $S" if $verbose;
(substr($S, $index,1), substr($S, $index + $O,1) ) =
(substr($S, $index + $O, 1), substr($S, $index, 1));
}
say $S;
Running it:
$ ./charswap-perl
pndraerlaku
$ ./charswap-perl 'perlandraku' 5 4
perlandraku
pdraknerlau
#! /usr/bin/env node
var S = process.argv[2] || 'perlandraku';
var N = S.length;
var O = process.argv[3] || 4;
var C = process.argv[4] || 3;
let die = (message) => {
console.log(message);
process.kill(process.pid);
};
var OO = parseInt(O); # [1]
if (OO != O || O < 1)
die('O: Integer >= 1 only');
var CC = parseInt(C);
if (CC != C || C < 1)
die('C: Integer >= 1 only');
if (C >= O)
die('C: Must be < O');
if (CC + OO > N) # [1a]
die('C + O: Must be <= N');
for (index = 1; index <= CC; index++) # [2]
{
S = S.slice(0, index) # [3]
+ S.charAt(index + OO) # [4]
+ S.slice(index +1, index + OO)
+ S.charAt(index)
+ S.slice(index + OO +1);
}
console.log(S);
[1] The input from the command line is taken as a string by default. That is mostly ok,
except that the +
operator (in [1a]) will happily do this «3 + 4 = 34», when
used on strings. Hence the parseInt
. This overloading of +
is
a design error, in my view.
[2] A C style for loop. How old fashion...
[3] A substring from the first index, upto the second (not including) if given, or to the end.
[4] A single character, at the given position.
Running it:
$ ./charswap-node
pndraerlaku
$ ./charswap-node "This is not the end" 9 7
Tt the enohis is nd
2 <= $N <= 5
.
$N-bit
gray code sequence.
[0, 1, 3, 2]
To generate the 3-bit Gray code sequence from the 2-bit Gray code sequence, follow the step below:
2-bit Gray Code sequence
[0, 1, 3, 2]
Binary form of the sequence
a) S1 = [00, 01, 11, 10]
Reverse of S1
b) S2 = [10, 11, 01, 00]
Prefix all entries of S1 with '0'
c) S1 = [000, 001, 011, 010]
Prefix all entries of S2 with '1'
d) S2 = [110, 111, 101, 100]
Concatenate S1 and S2 gives 3-bit Gray Code sequence
e) [000, 001, 011, 010, 110, 111, 101, 100]
3-bit Gray Code sequence
[0, 1, 3, 2, 6, 7, 5, 4]
Example
Input: $N = 4
Output: [0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8]
Observation 1
We are given the 2-bit Gray Code sequence, Then we do the steps above, and end up with
the 3-bit Gray Code sequence. If we want the 4-bit Gray Code sequence, we
simply do the steps above once more (on the 3-bit Gray Code sequence). And so on.
In a loop.
Observation 2
The binary form of the sequence (marked «a)» above have leading zeroes on some of the values,
so that all the numbers have the same length. The length is 1 less than the sequence bit number.
The program should be easy to understand, especially if you map the verbose output to the steps in the challenge.
File: gray-code-seq
#! /usr/bin/env raku
unit sub MAIN (Int $N where 2 <= $N <=5 = 3,
:$v, :$verbose = $v);
my @sequence = (0,1,3,2);
for 3 .. $N -> $level
{
@sequence = generate2bgcs(@sequence, $level, $verbose);
}
say "[{ @sequence.join(", ") }]";
sub generate2bgcs (@in, $level, $verbose)
{
my @S1a = @in.map({ .fmt('%0' ~ ($level-1) ~ 'b') }); # [1]
my @S2a = @S1a.reverse;
my @S1 = @S1a.map({ "0$_" });
my @S2 = @S2a.map({ "1$_" });
my @S3 = (@S1, @S2).flat; # [2]
my @out = @S3.map( *.parse-base(2) ); # [3]
if $verbose
{
say ": a) S1 = [{ @S1a.join(", ") }]";
say ": b) S2 = [{ @S2a.join(", ") }]";
say ": c) S1 = [{ @S1.join(", ") }]";
say ": b) S2 = [{ @S2.join(", ") }]";
say ": e) S3 = [{ @S3.join(", ") }]";
say ": \$N=$level = [{ @out.join(", ") }]";
}
return @out;
}
[1] Using the fmt
metod to convert the (decimal) number to binary
(the «b» flag). We want it to have at least $level-1
characters, and we pad with
zeroes from the start (e.g. 1.fmt('%03d') => 001
).
[2] We want one array with all the elements, not an array with two elemens (the content of «@S1» and «@S2».)
[3] using parse-base(2)
to interpret the value as a binary
number, and converting it to a (decimal) number.
See
docs.raku.org/routine/fmt
for more information about fmt
.
See
docs.raku.org/routine/parse-base
for more information about parse-base
.
Running it:
$ ./gray-code-seq
[0, 1, 3, 2, 6, 7, 5, 4]
$ ./gray-code-seq 4
[0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8]
$ ./gray-code-seq -v
: a) S1 = [00, 01, 11, 10]
: b) S2 = [10, 11, 01, 00]
: c) S1 = [000, 001, 011, 010]
: b) S2 = [110, 111, 101, 100]
: e) S3 = [000, 001, 011, 010, 110, 111, 101, 100]
: $N=3 = [0, 1, 3, 2, 6, 7, 5, 4]
[0, 1, 3, 2, 6, 7, 5, 4]
$ ./gray-code-seq 5
[0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8, 24, 25, 27, 26, 30, \
31, 29, 28, 20, 21, 23, 22, 18, 19, 17, 16]
$ ./gray-code-seq -v 4
: a) S1 = [00, 01, 11, 10]
: b) S2 = [10, 11, 01, 00]
: c) S1 = [000, 001, 011, 010]
: b) S2 = [110, 111, 101, 100]
: e) S3 = [000, 001, 011, 010, 110, 111, 101, 100]
: $N=3 = [0, 1, 3, 2, 6, 7, 5, 4]
: a) S1 = [000, 001, 011, 010, 110, 111, 101, 100]
: b) S2 = [100, 101, 111, 110, 010, 011, 001, 000]
: c) S1 = [0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100]
: b) S2 = [1100, 1101, 1111, 1110, 1010, 1011, 1001, 1000]
: e) S3 = [0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100, 1100, 1101, 1111, \
1110, 1010, 1011, 1001, 1000]
: $N=4 = [0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8]
[0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8]
$ ./gray-code-seq -v 5
: a) S1 = [00, 01, 11, 10]
: b) S2 = [10, 11, 01, 00]
: c) S1 = [000, 001, 011, 010]
: b) S2 = [110, 111, 101, 100]
: e) S3 = [000, 001, 011, 010, 110, 111, 101, 100]
: $N=3 = [0, 1, 3, 2, 6, 7, 5, 4]
: a) S1 = [000, 001, 011, 010, 110, 111, 101, 100]
: b) S2 = [100, 101, 111, 110, 010, 011, 001, 000]
: c) S1 = [0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100]
: b) S2 = [1100, 1101, 1111, 1110, 1010, 1011, 1001, 1000]
: e) S3 = [0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100, 1100, 1101, 1111, \
1110, 1010, 1011, 1001, 1000]
: $N=4 = [0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8]
: a) S1 = [0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100, 1100, 1101, 1111, \
1110, 1010, 1011, 1001, 1000]
: b) S2 = [1000, 1001, 1011, 1010, 1110, 1111, 1101, 1100, 0100, 0101, 0111, \
0110, 0010, 0011, 0001, 0000]
: c) S1 = [00000, 00001, 00011, 00010, 00110, 00111, 00101, 00100, 01100, \
01101, 01111, 01110, 01010, 01011, 01001, 01000]
: b) S2 = [11000, 11001, 11011, 11010, 11110, 11111, 11101, 11100, 10100, \
10101, 10111, 10110, 10010, 10011, 10001, 10000]
: e) S3 = [00000, 00001, 00011, 00010, 00110, 00111, 00101, 00100, 01100, \
01101, 01111, 01110, 01010, 01011, 01001, 01000, 11000, 11001, 11011, \
11010, 11110, 11111, 11101, 11100, 10100, 10101, 10111, 10110, 10010, \
10011, 10001, 10000]
: $N=5 = [0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8, 24, 25, 27, \
26, 30, 31, 29, 28, 20, 21, 23, 22, 18, 19, 17, 16]
[0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8, 24, 25, 27, 26, 30, \
31, 29, 28, 20, 21, 23, 22, 18, 19, 17, 16]
#! /usr/bin/env perl
use strict;
use feature 'say';
use feature 'signatures';
no warnings 'experimental::signatures';
my $verbose = 0;
if (@ARGV && $ARGV[0] eq "--verbose")
{
$verbose = 1;
shift(@ARGV);
}
my $N = shift(@ARGV) // 3;
die '$N out of range (2..5)' unless 2 <= $N && $N <=5;
my @sequence = (0,1,3,2);
for my $level (3 .. $N)
{
@sequence = generate2bgcs($level, $verbose, @sequence);
}
say "[" . join(", ", @sequence) . "]";
sub generate2bgcs ($level, $verbose, @in)
{
my @S1a = map { sprintf('%0' . ($level-1) . 'b', $_) } @in;
my @S2a = reverse @S1a;
my @S1 = map { "0$_" } @S1a;
my @S2 = map { "1$_" } @S2a;
my @S3 = (@S1, @S2);
my @out = map { oct("0b$_") } @S3;
if ($verbose)
{
say ": a) S1 = [" . join(", ", @S1a) ."]";
say ": b) S2 = [" . join(", ", @S2a) ."]";
say ": c) S1 = [" . join(", ", @S1) ."]";
say ": b) S2 = [" . join(", ", @S2) ."]";
say ": e) S3 = [" . join(", ", @S3) ."]";
say ": \$N=$level = [" . join(", ", @out) . "]";
}
return @out;
}
Running it gives the same output.
Except that it is more forgiving. Specifying a decimal number works here, but not in the Raku version:
$ ./gray-code-seq-perl 2.99
[0, 1, 3, 2]
$ ./gray-code-seq-perl 3.14
[0, 1, 3, 2, 6, 7, 5, 4]
$ ./gray-code-seq 3.14
Usage:
./gray-code-seq [-v=<Any>] [--verbose=<Any>] [<N>]
#! /usr/bin/env node
var N = process.argv[2] || 3;
var verbose = false;
if (N == '--verbose')
{
verbose = true;
N = process.argv[3] || 3;
}
let die = (message) => {
console.log(message);
process.kill(process.pid);
};
var NN = parseInt(N);
if (NN != N || NN < 2 || NN > 5)
die('N: Integer 2,3,4,5 only');
var sequence = [0,1,3,2];
let asBinary = (value, length) => {
var binary = value.toString(2);
while (binary.length < length)
binary = "0" + binary;
return binary;
}
let generate2bgcs = (array, level) => {
var S1a = array.map(x => asBinary(x,level-1) );
var S2a = S1a.slice(); S2a.reverse();
var S1 = S1a.map(x => '0' + x);
var S2 = S2a.map(x => '1' + x);
var S3 = S1; for (x of S2) { S3.push(x); }
var out = S3.map( x => parseInt(x, 2) );
if (verbose)
{
console.log(': in: ' + array);
console.log(': a) S1 = ' + S1a);
console.log(': b) S2 = ' + S2a);
console.log(': c) S1 = ' + S1);
console.log(': d) S2 = ' + S2);
console.log(': e) S3 = ' + S3);
console.log(': out: ' + out);
}
return out;
}
for (level = 3; level <= NN; level++)
{
sequence = generate2bgcs(sequence, level);
}
Note that map
(in Node and JS)
returns a new array with the changes applied (and leaves the original array unchanged),
but reverse
changes the array itself. Another design error?
Running it:
$ ./gray-code-seq-node 2.99
N: Integer 2,3,4,5 only
Terminated
$ ./gray-code-seq-node 3
[ 0, 1, 3, 2, 6, 7, 5, 4 ]
$ ./gray-code-seq-node 4
[ 0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8 ]
$ ./gray-code-seq-node --verbose 3
: in: 0,1,3,2
: a) S1 = 00,01,11,10
: b) S2 = 10,11,01,00
: c) S1 = 000,001,011,010,110,111,101,100
: d) S2 = 110,111,101,100
: e) S3 = 000,001,011,010,110,111,101,100
: out: 0,1,3,2,6,7,5,4
[ 0, 1, 3, 2, 6, 7, 5, 4 ]
$ ./gray-code-seq-node --verbose 4
: in: 0,1,3,2
: a) S1 = 00,01,11,10
: b) S2 = 10,11,01,00
: c) S1 = 000,001,011,010,110,111,101,100
: d) S2 = 110,111,101,100
: e) S3 = 000,001,011,010,110,111,101,100
: out: 0,1,3,2,6,7,5,4
: in: 0,1,3,2,6,7,5,4
: a) S1 = 000,001,011,010,110,111,101,100
: b) S2 = 100,101,111,110,010,011,001,000
: c) S1 = 0000,0001,0011,0010,0110,0111,0101,0100,1100,1101,1111,1110,\
1010,1011,1001,1000
: d) S2 = 1100,1101,1111,1110,1010,1011,1001,1000
: e) S3 = 0000,0001,0011,0010,0110,0111,0101,0100,1100,1101,1111,1110,\
1010,1011,1001,1000
: out: 0,1,3,2,6,7,5,4,12,13,15,14,10,11,9,8
[ 0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8 ]
Note the extra space after the opening bracket, and before the closing one. So this is not strictly answering the challenge. Oh well.
And that's it.