This is my response to The Weekly Challenge #231.
Input: @ints = (3, 2, 1, 4)
Output: (3, 2)
The minimum is 1 and maximum is 4 in the given array.
So (3, 2) is neither min nor max.
Example 2:
Input: @ints = (3, 1)
Output: -1
Example 3:
Input: @ints = (2, 1, 3)
Output: (2)
The minimum is 1 and maximum is 3 in the given array.
So 2 is neither min nor max.
#! /usr/bin/env raku
unit sub MAIN (*@ints where @ints.elems == @ints.unique.elems > 0 # [1]
&& all(@ints) ~~ Int); # [2]
if @ints.elems < 3 # [3]
{
say "-1"; # [3]
}
else
{
my @min-max = @ints.grep( @ints.min < * < @ints.max ); # [4]
say "({ @min-max.join(", ") })"; # [5]
}
[1] Ensure that the values (of which there are at least 1)
are distinct by comparing the number of elements of the original array with
one without duplicates, courtesy of unique
.
See
docs.raku.org/routine/unique
for more information about unique
.
[2] All the values must be integers.
[3] 1 or 2 elements only? Print the «-1» error message.
[4]
Get the values that are higher than the lowest value
(min
) and lower than the highest value
(max
).
See
docs.raku.org/routine/min
for more information about min
.
See
docs.raku.org/routine/max
for more information about max
.
[5] Print the result, comma separated and enclosed in parens.
Running it:
$ ./min-max 3 2 1 4
(3, 2)
$ ./min-max 3 1
-1
$ ./min-max 2 1 3
(2)
Looking good.
Input: @list = ("7868190130M7522","5303914400F9211","9273338290F4010")
Ouput: 2
The age of the passengers in the given list are 75, 92 and 40.
So we have only 2 senior citizens.
Example 2:
Input: @list = ("1313579440F2036","2921522980M5644")
Ouput: 0
#! /usr/bin/env raku
subset PassengerDetails
where * ~~ /^ <[0..9]> ** 10 <[MF]> <[0..9]> ** 2 <[0..9]> ** 2 $/; # [1]
unit sub MAIN (*@list where @list.elems > 0 # [2]
&& all(@list) ~~ PassengerDetails,
:v(:$verbose));
my $senior = 0; # [3]
for @list -> $passenger # [4]
{
my $age = $passenger.substr(11,2); # [5]
if $age >= 60 # [6]
{
say ": Passenger $passenger (age $age - senior)" if $verbose;
$senior++; # [6a]
}
elsif $verbose
{
say ": Passenger $passenger (age $age)";
}
}
say $senior; # [7]
[1] A custom type (with subset
) to ensure that
we get legal passenger detail strings only.
See
docs.raku.org/language/typesystem#index-entry-subset-subset
for more information about subset
.
[2] Use the custom type (from [1]), and make sure that we get at least one of them.
[3] The result (the count) will end up here.
[4] Iterate over the passengers.
[5] Get the age part. It is part of a longer string
(also called a substring) at a fixed location, so substr
does
the trick.
See
docs.raku.org/routine/substr
for more information about substr
.
[6] Increase the counter if we have a senior citizen.
[7] Print the result.
Running it:
$ ./senior-citizens-subset 7868190130M7522 5303914400F9211 9273338290F4010
2
$ ./senior-citizens-subset 1313579440F2036 2921522980M5644
0
Looking good.
With verbose mode:
$ ./senior-citizens-subset -v 7868190130M7522 5303914400F9211 9273338290F4010
: Passenger 7868190130M7522 (age 75 - senior)
: Passenger 5303914400F9211 (age 92 - senior)
: Passenger 9273338290F4010 (age 40)
2
$ ./senior-citizens-subset -v 1313579440F2036 2921522980M5644
: Passenger 1313579440F2036 (age 20)
: Passenger 2921522980M5644 (age 56)
0
The custom type (subset
) is only used to validate the input, but
we can use it (or rather a grammarified version of it) to retrieve the values
(or rather, the age):
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 0, :v(:$verbose));
grammar PassengerDetails # [1]
{
token TOP { ^ <phone> <sex> <age> <seat> $ } # [2]
token phone { <[0..9]> ** 10 }
token sex { <[MF]> }
token age { <[0..9]> ** 2 }
token seat { <[0..9]> ** 2 }
}
my $senior = 0;
for @list -> $passenger
{
my $p = PassengerDetails.parse($passenger) # [3]
|| die "Illegal argument $passenger";
my $age = $p<age>; # [4]
if $age >= 60
{
say ": Passenger $passenger (age $age - senior)" if $verbose;
$senior++;
}
elsif $verbose
{
say ": Passenger $passenger (age $age)";
}
}
say $senior;
[1] A grammar
set up with the fields that the
strings must contain.
[2] The «TOP» token is the entry point. The token
keyword is one of three we can use in Grammars; the others are regex
and rule
.
[3] Parse the passenger details, with the grammer from [1]. Terminate the program if the input did not match.
[4] Retrieve the «age» part.
Running it gives the expected result, but input with errors are not catched
by the where
clause, as we got rid of the custom type.
$ ./senior-citizens-grammar -v 7868190130M7522 5303914400F9211 9273338290F4010
: Passenger 7868190130M7522 (age 75 - senior)
: Passenger 5303914400F9211 (age 92 - senior)
: Passenger 9273338290F4010 (age 40)
2
$ ./senior-citizens-grammar -v 7868190130M7522 5303914400F9211 9273338290F4010x
: Passenger 7868190130M7522 (age 75 - senior)
: Passenger 5303914400F9211 (age 92 - senior)
Illegal argument 9273338290F4010x
in sub MAIN at ./senior-citizens-grammar line 20
in block <unit> at ./senior-citizens-grammar line 1
The result (and downside) is that the error message comes after a lot of verbose output. That is not very nice.
We can fix that, by using the grammar in the where
clause, but it
requires a lot of extra hoops:
where
clause, so we have
to define said grammar before the MAIN
thingy
unit sub
after we have started writing code,
so we have to remove the unit
part and explicitly add the formerly
implied block
all
junction), but a map
and the parse
method on each one will do the job. Almost. The result of the map
part is coerced to a Boolean value (by the &&
operator), and
that will blow up the program (with the very unhelpful «This type cannot
unbox to a native integer: P6opaque, PassengerDetails» error message. The
remedy: coerce the parse objects to a Boolean value (with so
)
See
docs.raku.org/routine/so for more
for more information about the Boolean Context Operator so
.
#! /usr/bin/env raku
grammar PassengerDetails
{
token TOP { ^ <phone> <sex> <age> <seat> $ }
token phone { <[0..9]> ** 10 }
token sex { <[MF]> }
token age { <[0..9]> ** 2 }
token seat { <[0..9]> ** 2 }
}
sub MAIN (*@list where @list.elems > 0
&& @list.map( so PassengerDetails.parse(*) ),
:v(:$verbose))
{
my $senior = 0;
for @list -> $passenger
{
my $p = PassengerDetails.parse($passenger);
my $age = $p<age>;
if $age >= 60
{
say ": Passenger $passenger (age $age - senior)" if $verbose;
$senior++;
}
elsif $verbose
{
say ": Passenger $passenger (age $age)";
}
}
say $senior;
}
Running it gives the expected result, with legal as well as illegal input:
$ ./senior-citizens -v 7868190130M7520 5303914400F9211 9273338290F4010
: Passenger 7868190130M7520 (age 75 - senior)
: Passenger 5303914400F9211 (age 92 - senior)
: Passenger 9273338290F4010 (age 40)
2
$ ./senior-citizens -v 7868190130M7520 5303914400F9211 9273338290F4010xc
Usage:
./senior-citizens [-v|--verbose[=Any]] [<list> ...]
And that's it.