Self Order
with Raku

by Arne Sommer

Self Order with Raku

[307] Published 13. September 2024.

This is my response to The Weekly Challenge #286.

Challenge #286.1: Self Spammer

Write a program which outputs one word of its own script / source code at random. A word is anything between whitespace, including symbols.

Example 1:
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
File: self-spammer
#! /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(",")

Challenge #286.2: Order Game

You are given an array of integers, @ints, whose length is a power of 2.

Write a script to play the order game (min and max) and return the last element.

Example 1:
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
File: order-game
#! /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.