This is my response to The Weekly Challenge #215.
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
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
#! /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.