Odd Placement
with Raku

by Arne Sommer

Odd Placement with Raku

[235] Published 7. May 2023.

This is my response to The Weekly Challenge #215.

Challenge #215.1: Odd one Out

You are given a list of words (alphabetic characters only) of same size.

Write a script to remove all words not sorted alphabetically and print the number of words in the list that are not alphabetically sorted.

Example 1:
Input: @words = ('abc', 'xyz', 'tsu')
Output: 1

The words 'abc' and 'xyz' are sorted and can't be removed.
The word 'tsu' is not sorted and hence can be removed.
Example 2:
Input: @words = ('rat', 'cab', 'dad')
Output: 3

None of the words in the given list are sorted.
Therefore all three needs to be removed.
Example 3:
Input: @words = ('x', 'y', 'z')
Output: 0

Note that the challenge does not say that upper case letters are illegal, but we can choose to infer that from the examples - thus avoiding the problem of sorting lowercase and uppercase letters.

File: odd-one-out
#! /usr/bin/env raku

unit sub MAIN (*@words where @words.elems > 0
                  && all(@words) ~~ /^<[a..z]>+$/,  # [1]
               :v(:$verbose));

my @odd = @words.grep({ ! [<=] $_.ords });          # [2]

say ":Odd word(s): { @odd.map({ "'$_'" }).join(",") }" if $verbose;

say @odd.elems;                                     # [3]

[1] A slurpy array with at least one element, and all of them (set up with an all junction) must contain one or more of the english (lower case) letters a to z and nothing else.

See docs.raku.org/routine/all for more information about the all Junction.

[2] Get the odd words, using grep on the original array of words. We start with the word itself ($_), then we use ords to translating each character in the string to a UTF-8 (Unicode) codepoint (which for english letters is the same as the ascii and isolatin code). Then we reduce that list (of codepoint integers) to a single Boolean value with the Reduction Metaoperator [] and the numerical comparison operator <=. This will return True if all the values are in non-descending order (i.e. the same or higher). Then we negate that with a prefix !. The result is a list of non-compliant words.

See docs.raku.org/routine/grep for more information about grep.

See docs.raku.org/routine/ords for more information about ords.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

[3] Then we count the number of elements in the array from [2] and print it.

Running it:

$ ./odd-one-out abc xyz tsu
1

$ ./odd-one-out rat cab dad
3

$ ./odd-one-out x y z
0

Looking good.

With verbose mode:

$ ./odd-one-out -v abc xyz tsu
:Odd word(s): 'tsu'
1

$ ./odd-one-out -v rat cab dad
:Odd word(s): 'rat','cab','dad'
3

$ ./odd-one-out -v x y z
:Odd word(s): 
0

This looks very much like a candidate for a one liner. And here it is, sort of:

File: odd-one-out-oneliner
#! /usr/bin/env raku

unit sub MAIN (*@words where @words.elems > 0 && all(@words) ~~ /^<[a..z]>+$/);

say @words.grep({ ! [<=] $_.ords }).elems;

Note that verbose mode had to go.

$ ./odd-one-out-oneliner abc xyz tsu
1

$ ./odd-one-out-oneliner rat cab dad
3

$ ./odd-one-out-oneliner x y z
0

Challenge #215.2: Number Placement

You are given a list of numbers having just 0 and 1. You are also given placement count (>=1).

Write a script to find out if it is possible to replace 0 with 1 in the given list. The only condition is that you can only replace when there is no 1 on either side. Print 1 if it is possible otherwise 0.

Example 1:
Input: @numbers = (1,0,0,0,1), $count = 1
Output: 1

You are asked to replace only one 0 as given count is 1.
We can easily replace middle 0 in the list i.e. (1,0,1,0,1).
Example 2:
Input: @numbers = (1,0,0,0,1), $count = 2
Output: 0

You are asked to replace two 0's as given count is 2.
It is impossible to replace two 0's.
Example 3:
Input: @numbers = (1,0,0,0,0,0,0,0,1), $count = 3
Output: 1
File: number-placement
#! /usr/bin/env raku

unit sub MAIN (UInt :c(:$count) where $count > 0;  # [1]
               *@numbers where @numbers.elems > 0
                 && all(@numbers) eq "0" | "1",    # [1a]
               :v(:$verbose));

my @num      = @numbers.clone;                     # [2]
my $size     = @num.elems;                         # [3] 
my $replaced = 0;                                  # [4]

for ^$count                                        # [5]
{
  for 0 .. $size -3 -> $start                      # [6]
  {
    print ":Start at $start. Values: \
      [{ @num[$start .. $start +2].join(",") }]" if $verbose;

    if all(@num[$start .. $start +2]) == 0         # [7]
    {
      @num[$start +1] = 1;                         # [7a]
      say " -> replaced with [@num[$start],1,@num[$start+2]]" if $verbose;
      $replaced++;                                 # [7b]
      last;                                        # [7c]
    }
    elsif $verbose
    {
      say "";
    }
  }
}

say ":Numbers: { @num.join(",") }" if $verbose;

say + ($count == $replaced);                       # [9]

[1] A named argument for the count, followed by a slurpy array containing the numbers. There must be at least one, and they can only hold the values 0 or 1 [1a].

[2] As we cannot change the original array. Try it, and you will be told that you «cannot assign to an immutable value».

[3] The size of the array.

[4] The number of replacements we have managed to do will end up here.

[5] We are asked to do $count replacements, so let us try do do just that (with a loop).

[6] Start at index 0 and go on until the end (which is three positions before the actual end, as we require three adjacent values for the replacement consideration (left, the value to replace or not, right).

[7] Are all the three values (starting at the current index) zero? If so, replace the value itself [7a], increase the replacement counter [7b], and exit the for loop [7c] - as we do not want more than one replacement in this loop. The outer loop (in [5]) takes care of the rest.

[8] Were we able to do the required number of replacements? Note the prefix + so that we end up with «1» or «0» instead of «True» or «False».

Running it:

$ ./number-placement -c=1 1 0 0 0 1
1

$ ./number-placement -c=2 1 0 0 0 1
0

$ ./number-placement -c=3 1 0 0 0 0 0 0 0 1
1

Looking good.

With verbose mode:

$  ./number-placement -v -c=1 1 0 0 0 1
:Start at 0. Values: [1,0,0]
:Start at 1. Values: [0,0,0] -> replaced with [0,1,0]
:Numbers: 1,0,1,0,1
1

$ ./number-placement -v -c=2 1 0 0 0 1
:Start at 0. Values: [1,0,0]
:Start at 1. Values: [0,0,0] -> replaced with [0,1,0]
:Start at 0. Values: [1,0,1]
:Start at 1. Values: [0,1,0]
:Start at 2. Values: [1,0,1]
:Numbers: 1,0,1,0,1
0

$ ./number-placement -v -c=3 1 0 0 0 0 0 0 0 1
:Start at 0. Values: [1,0,0]
:Start at 1. Values: [0,0,0] -> replaced with [0,1,0]
:Start at 0. Values: [1,0,1]
:Start at 1. Values: [0,1,0]
:Start at 2. Values: [1,0,0]
:Start at 3. Values: [0,0,0] -> replaced with [0,1,0]
:Start at 0. Values: [1,0,1]
:Start at 1. Values: [0,1,0]
:Start at 2. Values: [1,0,1]
:Start at 3. Values: [0,1,0]
:Start at 4. Values: [1,0,0]
:Start at 5. Values: [0,0,0] -> replaced with [0,1,0]
:Numbers: 1,0,1,0,1,0,1,0,1
1

And that's it.