This is my response to The Weekly Challenge #286.
If the source code contains a line such as:
'open my $fh, "<", "ch-1.pl" or die;'
then the program would output each of the words
{ open, my, $fh,, "<",, "ch-1.pl", or, die; }
(along with other words in the source) with some positive probability.
Example 2:
Technically 'print(" hello ");' is *not* an example program, because it
does not assign positive probability to the other two words in the
script. It will never display print(" or ");
Example 3:
An empty script is one trivial solution, and here is another:
echo "42" > ch-1.pl && perl -p -e '' ch-1.pl
#! /usr/bin/env raku
unit sub MAIN (:v(:$verbose)); # [1]
my @words = $*PROGRAM.slurp.split(/\s+/); # [2]
say ": words: { @words.map("'" ~ * ~ "'").join(",") }" if $verbose; # [1a]
say @words.pick; # [3]
[1] This one sets up verbose mode only, where (in [1a]) we pretty print the "words" given by [2].
[2]
The special variable $*PROGRAM
gives us an IO::Path object
representing the current script. Then we apply slurp
to this object to
read in the whole content of it in one go. And finally we split the content
on space(s), to get a list of "words".
See docs.raku.org/language/variables#$*PROGRAM
for more information about $*PROGRAM
.
See docs.raku.org/routine/slurp for more information about slurp
.
[3] Choose a random word from the list (with pick
), and
print it.
See docs.raku.org/routine/pick for more information about pick
.
Running it gives unpredictable results:
$ ./self-spammer
{
$ ./self-spammer
(:v(:$verbose));
$ ./self-spammer
"'").join(",")
$ ./self-spammer
$verbose;
$ ./self-spammer
$verbose;
$ ./self-spammer
unit
Looking good, in a way...
With verbose mode:
$ ./self-spammer -v
: words: '#!','/usr/bin/env','raku','unit','sub','MAIN','(:v(:$verbose));',
'my','@words','=','$*PROGRAM.slurp.split(/\s+/);','say','":','words:',
'{','@words.map("'"','~','*','~','"'").join(",")','}"','if','$verbose;',
'say','@words.pick;'
"'").join(",")
@ints
, whose length is a power of 2.
Input: @ints = (2, 1, 4, 5, 6, 3, 0, 2)
Output: 1
Operation 1:
min(2, 1) = 1
max(4, 5) = 5
min(6, 3) = 3
max(0, 2) = 2
Operation 2:
min(1, 5) = 1
max(3, 2) = 3
Operation 3:
min(1, 3) = 1
Example 2:
Input: @ints = (0, 5, 3, 2)
Output: 0
Operation 1:
min(0, 5) = 0
max(3, 2) = 3
Operation 2:
min(0, 3) = 0
Example 3:
Input: @ints = (9, 2, 1, 4, 5, 6, 0, 7, 3, 1, 3, 5, 7, 9, 0, 8)
Output: 2
Operation 1:
min(9, 2) = 2
max(1, 4) = 4
min(5, 6) = 5
max(0, 7) = 7
min(3, 1) = 1
max(3, 5) = 5
min(7, 9) = 7
max(0, 8) = 8
Operation 2:
min(2, 4) = 2
max(5, 7) = 7
min(1, 5) = 1
max(7, 8) = 8
Operation 3:
min(2, 7) = 2
max(1, 8) = 8
Operation 4:
min(2, 8) = 2
#! /usr/bin/env raku
my $powtwo := (1, 2, 4, 8 ... *); # [1]
unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0, # [2]
:v(:$verbose));
my $length = @ints.elems; # [3]
for $powtwo -> $int # [4]
{
last if $int == $length; # [4a]
die 'Wrong number of @ints. Should be power of two' if $int > $length; # [4b]
}
while @ints.elems > 1 # [5]
{
say ":\@ints: ({ @ints.join(",") })" if $verbose;
my @new; # [6]
my $min = True; # [7]
for @ints -> $first, $second # [8]
{
@new.push($min ?? min($first, $second) !! max($first, $second) ); # [9]
$min = ! $min; # [10]
}
@ints = @new; # [11]
}
say @ints.first; # [12]
[1] A lazy sequence of power-of-two values.
[2] We cannot use the sequence from [1] directly here, but we could have
added && @ints.elems %% 2
before the comma on this line and
dropped [1] and the [4] block to satisfy the given examples. But adding
an actual power-of-two rule is a nice touch.
[3] Get the length of the input array.
[4] Iterate over the power-of-two values, and exit the loop
(with last
) if we find the number in the sequence [4a]. Exit the
program if we reach a higher number (i.e. it is not in the sequence)
[4b].
See docs.raku.org/routine/last for more information about last
.
[5] Until we have reduced the list to a single element.
[6] The replacement list in this iteration will end up here.
[7] We start with the min
operation.
[8] Iterate over the list, two elements at a time.
[9]
Add the minimum (min
) or maximum (max
) of them,
according to the Boolean variable from [7].
See docs.raku.org/routine/min for more information about min
.
See docs.raku.org/routine/max for more information about max
.
[10] Toggle (negate) the Boolean variable.
[11] Replace the original list with the new one.
[12] We have a list with one element now (courtesy of [5]).
It is still a list, so we print the first
element.
See docs.raku.org/routine/first for more information about first
.
Running it:
$ ./order-game 2 1 4 5 6 3 0 2
1
$ ./order-game 0 5 3 2
0
$ ./order-game 9 2 1 4 5 6 0 7 3 1 3 5 7 9 0 8
2
Looking good.
With verbose mode:
$ ./order-game -v 2 1 4 5 6 3 0 2
:@ints: (2,1,4,5,6,3,0,2)
:@ints: (1,5,3,2)
:@ints: (1,3)
1
$ ./order-game -v 0 5 3 2
:@ints: (0,5,3,2)
:@ints: (0,3)
0
$ ./order-game -v 9 2 1 4 5 6 0 7 3 1 3 5 7 9 0 8
:@ints: (9,2,1,4,5,6,0,7,3,1,3,5,7,9,0,8)
:@ints: (2,4,5,7,1,5,7,8)
:@ints: (2,7,1,8)
:@ints: (2,8)
2
And that's it.