This is my response to The Weekly Challenge #274.
$sentance
.
Goat Latin
, a made up language
similar to Pig Latin
.
Goat Latin
:
Input: $sentence = "I love Perl"
Output: "Imaa ovelmaaa erlPmaaaa"
Example 2:
Input: $sentence = "Perl and Raku are friends"
Output: "erlPmaa andmaaa akuRmaaaa aremaaaaa riendsfmaaaaaa"
Example 3:
Input: $sentence = "The Weekly Challenge"
Output: "heTmaa eeklyWmaaa hallengeCmaaaa"
#! /usr/bin/env raku
unit sub MAIN ($sentence where $sentence.chars > 0); # [1]
say $sentence.words.map( *.&goatify-word ).join(" "); # [2]
sub goatify-word ($word is copy) # [3]
{
state $add = 2; # [4]
$word = $word.substr(1) ~ $word.substr(0,1)
if $word !~~ /^<[aeiou]>/; # [5]
$word ~= "m" ~ "a" x $add++; # [6]
return $word; # [7]
}
[1] Ensure at least one character. Still not a real sentence, but better than nothing.
[2] Split the sentence into words (with words
), apply the procedure
defined below with the special call-a-function-as-a-method syntax, and
finally join the words together (separated by a space) and print the
result.
[3] The proceure doing the conversion, so that the program (i.e. [2]) can
look like a one liner. The is copy
trait is there to make it
possible to change the value of the variable, which is a local copy.
[4] Use a state
variable to hold the number of «a»s to add
to the end of the words. Note that this approach will not support beeing
used multiple times - on new sentences - in the same program,
See
docs.raku.org/syntax/state for
more information about the variable declarator state
.
[5] If the first character (substr(0,1)
) of the word
is not a consonant (!~~ ^<[aeiou]>/
), move the first character to
the end.
See docs.raku.org/routine/substr for more information about substr
.
Note that I have used the vowel definition given in the challenge. There are a lot of additional Unicode vowels (e.g. the Norwegian Æ, Ø and Å), but they will be regarded as consonants by this program. As will digits and other non-letters.
[6] Add an «m» to the end of the word, and the number of «a»s according
to the $add
variable.
[7] Return the modified word.
Running it:
$ ./goat-latin "I love Perl"
Imaa ovelmaaa erlPmaaaa
$ ./goat-latin "Perl and Raku are friends"
erlPmaa andmaaa akuRmaaaa aremaaaaa riendsfmaaaaaa
$ ./goat-latin "The Weekly Challenge"
heTmaa eeklyWmaaa hallengeCmaaaa
Looking good.
Input: [ [12, 11, 41], [15, 5, 35] ]
Output: [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
Route 1 leaves every 12 minutes, starting at 11 minutes past the hour
(so 11, 23, ...) and takes 41 minutes. Route 2 leaves every 15 minutes,
starting at 5 minutes past (5, 20, ...) and takes 35 minutes.
At 45 minutes past the hour I could take the route 1 bus at 47 past the
hour, arriving at 28 minutes past the following hour, but if I wait for
the route 2 bus at 50 past I will get to town sooner, at 25 minutes past
the next hour.
Example 2:
Input: [ [12, 3, 41], [15, 9, 35], [30, 5, 25] ]
Output: [ 0, 1, 2, 3, 25, 26, 27, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 55, 56, 57, 58, 59 ]
#! /usr/bin/env raku
unit sub MAIN (Str $routestr = "12 11 41 | 15 5 35", :v(:$verbose)); # [1]
my @routes = $routestr.split("|")>>.words>>.Numeric; # [2]
die "All the routes must have three integers each"
unless all(@routes>>.elems) == 3; # [3]
die "Non-negative integers only" unless all(@routes[*;*]) ~~ UInt; # [4]
my @departures; # [5]
for @routes -> @route # [6]
{
state $rid = 0; $rid++; # [7]
my $interval = @route[0]; # [8]
my $offset = @route[1]; # [8a]
my $duration = @route[2]; # [8b]
say ": Route: $rid Interval: $interval Offset: $offset Duration: $duration"
if $verbose;
die "Route $rid: Interval does not comply with 60 min rule"
unless 60 %% $interval; # [9]
die "Route $rid: Interval must be positive" unless $interval > 0; # [10]
my $min = $offset; # [11]
while $min < 60 # [12]
{
@departures.push: { dept => $min,
route => $rid,
arrival => $min + $duration
}; # [13]
$min += $interval; # [14]
}
}
my @sorted = @departures.sort({ $^a<dept> <=> $^b<dept> || # [15]
$^a<arrival> <=> $^b<arrival> || # [15a]
$^a<route> <=> $^b<route> }); # [15b]
if $verbose
{
for @sorted -> %dep
{
say ": Departure at { %dep<dept>.fmt('%02d') }: route %dep<route> \
arrive at %dep<arrival>";
}
}
exit if $verbose && $verbose eq '2'; # [v]
[1] Note the default routes, given on my matrix-as-a-string format.
[2] Split the route(s) string into a matrix. The >>.Numeric
part
will get rid of the IntStr
type for us (which would have made
verbose output ugly), and will enforce that we only get numeric values.
[3] All the rows (each one is a route) must have three elements.
[4] Ensure that the matrix contains unsigned integers (the UInt
type) only.
[5] The departures will end up here.
[6] Iterate over the routes (rows in the matrix).
[7] The route id, from 1 and counting. This is only used by verbose mode, and sorting (which is only relevent for verbose mode).
[8] The interval comes first, then the offset [8a] and finally the duration [8b].
[9] This will ensure that the second hour (and so on) will fit in just after the first one, with the same interval between all the departures - and the same departure times.
This 60 minute rule is not strictly necessary for us to answer the challenge, but it makes sense from a planning perspective. Let us say that we have an interval set to 50, with zero offset. The first departure is at 0:50, but is the second one at 1:50 (which would break the interval rule) or at 1:40 (which would make it impossible to say in advance when the bus will come without knowing which hour)?
[10] Ensure a positive interval between departures (i.e. not zero), thus avoiding an eternal loop in [12].
[11] The first departure is at the offset value.
[12] As long as we are within one hour.
[13] Add the departure to the list. I could have used a custom class instead of an hash here, but it is not worth the extra work in this case.
[14] Adjust the departure time, ready for the next iteration of [12].
[15] Now we have a list of departures, with route 1 first, followed by all ' the departures of route 2, and so on. We need to sort them, on the departure time. If there are more than one departure at the same time, sort them by arrival time (fastest bus first) [15a]. If we have several that are equally fast, sort them by route numbers [15b] to ensure the same verbose output each time.
[v] Let us run the program, up to this point and with verbose mode, to check that everything is in order:
$ ./bus-route -v=2
: Route: 1 Interval: 12 Offset: 11 Duration: 41
: Route: 2 Interval: 15 Offset: 5 Duration: 35
: Departure at 05: route 2 arrive at 40
: Departure at 11: route 1 arrive at 52
: Departure at 20: route 2 arrive at 55
: Departure at 23: route 1 arrive at 64
: Departure at 35: route 2 arrive at 70
: Departure at 35: route 1 arrive at 76
: Departure at 47: route 1 arrive at 88
: Departure at 50: route 2 arrive at 85
: Departure at 59: route 1 arrive at 100
$ ./bus-route -v=2 "12 3 41 | 15 9 35 | 30 5 25"
: Route: 1 Interval: 12 Offset: 3 Duration: 41
: Route: 2 Interval: 15 Offset: 9 Duration: 35
: Route: 3 Interval: 30 Offset: 5 Duration: 25
: Departure at 03: route 1 arrive at 44
: Departure at 05: route 3 arrive at 30
: Departure at 09: route 2 arrive at 44
: Departure at 15: route 1 arrive at 56
: Departure at 24: route 2 arrive at 59
: Departure at 27: route 1 arrive at 68
: Departure at 35: route 3 arrive at 60
: Departure at 39: route 2 arrive at 74
: Departure at 39: route 1 arrive at 80
: Departure at 51: route 1 arrive at 92
: Departure at 54: route 2 arrive at 89
Yes, everything seems to be in order.
Note the two departures at the same time (35 for the first example, and 39 for the second one). The best one (shortest) is shown first, in both cases.
Then the second half of the program:
File: bus-route (the rest)
my @skip;
for 0 .. 59 -> $min # [16]
{
my @todo = @sorted.grep( *<dept> >= $min ); # [17]
unless @todo.elems # [18]
{
if @skip && @skip[0] == 0 # [19]
{
@skip.push: $min; # [20]
say ": Min:$min (no more departures this hour, and we skipped the \
first one this hour) * skip" if $verbose;
}
elsif $verbose
{
say ": Min:$min (no more departures this hour, and we took the \
first one this hour) - first";
}
next; # [21]
}
my %first = @todo.shift; # [22]
my %wait_to = %first; # [23]
for @todo -> %dept # [24]
{
%wait_to = %dept if %dept<arrival> < %wait_to<arrival>; # [25]
}
my $ok = %first eqv %wait_to; # [26]
@skip.push: $min if !$ok; # [27]
say ": Min:{ $min.fmt('%02d') }: { $min == %wait_to<dept> ?? "" !! \
"wait to %wait_to<dept>" } take route %wait_to<route> and arrive at \
%wait_to<arrival> { $ok ?? "- first" !! "* skip" }" if $verbose;
}
say "[{ @skip.join(", ") }]"; # [28]
[16] Iterate over all the minutes in an hour.
[17] Only consider departures on or after that minute value.
[18] If there are no more departures this hour (as we e.g. have in the second example for minutes 55 to 59), we have to wait for the first departure of the next hour (which is the first departure of this hour, as we only consider a generic hour).
[19] Did we skip the 00 departure?
[20] If so, skip the current one as well (as we have to wait beyond the very first departure).
[21] We are done with this minute value.
[22] Get the first depature (which is a hash).
[23] We are waiting for this departure, initially set to the first departure as well.
[24] Iterate over the remaining departures.
[25] Wait for the iterated departure, if it arrives before the previously chosen one.
[26] Is it ok to take the first departure?
[27] Mark the minute as skipped if it is not ok.
[28] Print the result.
Running it:
$ ./bus-route
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
$ ./bus-route "12 3 41 | 15 9 35 | 30 5 25"
[0, 1, 2, 3, 25, 26, 27, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
55, 56, 57, 58, 59]
Looking good.
With verbose mode:
$ ./bus-route -v
: Route: 1 Interval: 12 Offset: 11 Duration: 41
: Route: 2 Interval: 15 Offset: 5 Duration: 35
: Departure at 05: route 2 arrive at 40
: Departure at 11: route 1 arrive at 52
: Departure at 20: route 2 arrive at 55
: Departure at 23: route 1 arrive at 64
: Departure at 35: route 2 arrive at 70
: Departure at 35: route 1 arrive at 76
: Departure at 47: route 1 arrive at 88
: Departure at 50: route 2 arrive at 85
: Departure at 59: route 1 arrive at 100
: Min:00: wait to 5 take route 2 and arrive at 40 - first
: Min:01: wait to 5 take route 2 and arrive at 40 - first
: Min:02: wait to 5 take route 2 and arrive at 40 - first
: Min:03: wait to 5 take route 2 and arrive at 40 - first
: Min:04: wait to 5 take route 2 and arrive at 40 - first
: Min:05: take route 2 and arrive at 40 - first
: Min:06: wait to 11 take route 1 and arrive at 52 - first
: Min:07: wait to 11 take route 1 and arrive at 52 - first
: Min:08: wait to 11 take route 1 and arrive at 52 - first
: Min:09: wait to 11 take route 1 and arrive at 52 - first
: Min:10: wait to 11 take route 1 and arrive at 52 - first
: Min:11: take route 1 and arrive at 52 - first
: Min:12: wait to 20 take route 2 and arrive at 55 - first
: Min:13: wait to 20 take route 2 and arrive at 55 - first
: Min:14: wait to 20 take route 2 and arrive at 55 - first
: Min:15: wait to 20 take route 2 and arrive at 55 - first
: Min:16: wait to 20 take route 2 and arrive at 55 - first
: Min:17: wait to 20 take route 2 and arrive at 55 - first
: Min:18: wait to 20 take route 2 and arrive at 55 - first
: Min:19: wait to 20 take route 2 and arrive at 55 - first
: Min:20: take route 2 and arrive at 55 - first
: Min:21: wait to 23 take route 1 and arrive at 64 - first
: Min:22: wait to 23 take route 1 and arrive at 64 - first
: Min:23: take route 1 and arrive at 64 - first
: Min:24: wait to 35 take route 2 and arrive at 70 - first
: Min:25: wait to 35 take route 2 and arrive at 70 - first
: Min:26: wait to 35 take route 2 and arrive at 70 - first
: Min:27: wait to 35 take route 2 and arrive at 70 - first
: Min:28: wait to 35 take route 2 and arrive at 70 - first
: Min:29: wait to 35 take route 2 and arrive at 70 - first
: Min:30: wait to 35 take route 2 and arrive at 70 - first
: Min:31: wait to 35 take route 2 and arrive at 70 - first
: Min:32: wait to 35 take route 2 and arrive at 70 - first
: Min:33: wait to 35 take route 2 and arrive at 70 - first
: Min:34: wait to 35 take route 2 and arrive at 70 - first
: Min:35: take route 2 and arrive at 70 - first
: Min:36: wait to 50 take route 2 and arrive at 85 * skip
: Min:37: wait to 50 take route 2 and arrive at 85 * skip
: Min:38: wait to 50 take route 2 and arrive at 85 * skip
: Min:39: wait to 50 take route 2 and arrive at 85 * skip
: Min:40: wait to 50 take route 2 and arrive at 85 * skip
: Min:41: wait to 50 take route 2 and arrive at 85 * skip
: Min:42: wait to 50 take route 2 and arrive at 85 * skip
: Min:43: wait to 50 take route 2 and arrive at 85 * skip
: Min:44: wait to 50 take route 2 and arrive at 85 * skip
: Min:45: wait to 50 take route 2 and arrive at 85 * skip
: Min:46: wait to 50 take route 2 and arrive at 85 * skip
: Min:47: wait to 50 take route 2 and arrive at 85 * skip
: Min:48: wait to 50 take route 2 and arrive at 85 - first
: Min:49: wait to 50 take route 2 and arrive at 85 - first
: Min:50: take route 2 and arrive at 85 - first
: Min:51: wait to 59 take route 1 and arrive at 100 - first
: Min:52: wait to 59 take route 1 and arrive at 100 - first
: Min:53: wait to 59 take route 1 and arrive at 100 - first
: Min:54: wait to 59 take route 1 and arrive at 100 - first
: Min:55: wait to 59 take route 1 and arrive at 100 - first
: Min:56: wait to 59 take route 1 and arrive at 100 - first
: Min:57: wait to 59 take route 1 and arrive at 100 - first
: Min:58: wait to 59 take route 1 and arrive at 100 - first
: Min:59: take route 1 and arrive at 100 - first
[36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
$ ./bus-route -v "12 3 41 | 15 9 35 | 30 5 25"
: Route: 1 Interval: 12 Offset: 3 Duration: 41
: Route: 2 Interval: 15 Offset: 9 Duration: 35
: Route: 3 Interval: 30 Offset: 5 Duration: 25
: Departure at 03: route 1 arrive at 44
: Departure at 05: route 3 arrive at 30
: Departure at 09: route 2 arrive at 44
: Departure at 15: route 1 arrive at 56
: Departure at 24: route 2 arrive at 59
: Departure at 27: route 1 arrive at 68
: Departure at 35: route 3 arrive at 60
: Departure at 39: route 2 arrive at 74
: Departure at 39: route 1 arrive at 80
: Departure at 51: route 1 arrive at 92
: Departure at 54: route 2 arrive at 89
: Min:00: wait to 5 take route 3 and arrive at 30 * skip
: Min:01: wait to 5 take route 3 and arrive at 30 * skip
: Min:02: wait to 5 take route 3 and arrive at 30 * skip
: Min:03: wait to 5 take route 3 and arrive at 30 * skip
: Min:04: wait to 5 take route 3 and arrive at 30 - first
: Min:05: take route 3 and arrive at 30 - first
: Min:06: wait to 9 take route 2 and arrive at 44 - first
: Min:07: wait to 9 take route 2 and arrive at 44 - first
: Min:08: wait to 9 take route 2 and arrive at 44 - first
: Min:09: take route 2 and arrive at 44 - first
: Min:10: wait to 15 take route 1 and arrive at 56 - first
: Min:11: wait to 15 take route 1 and arrive at 56 - first
: Min:12: wait to 15 take route 1 and arrive at 56 - first
: Min:13: wait to 15 take route 1 and arrive at 56 - first
: Min:14: wait to 15 take route 1 and arrive at 56 - first
: Min:15: take route 1 and arrive at 56 - first
: Min:16: wait to 24 take route 2 and arrive at 59 - first
: Min:17: wait to 24 take route 2 and arrive at 59 - first
: Min:18: wait to 24 take route 2 and arrive at 59 - first
: Min:19: wait to 24 take route 2 and arrive at 59 - first
: Min:20: wait to 24 take route 2 and arrive at 59 - first
: Min:21: wait to 24 take route 2 and arrive at 59 - first
: Min:22: wait to 24 take route 2 and arrive at 59 - first
: Min:23: wait to 24 take route 2 and arrive at 59 - first
: Min:24: take route 2 and arrive at 59 - first
: Min:25: wait to 35 take route 3 and arrive at 60 * skip
: Min:26: wait to 35 take route 3 and arrive at 60 * skip
: Min:27: wait to 35 take route 3 and arrive at 60 * skip
: Min:28: wait to 35 take route 3 and arrive at 60 - first
: Min:29: wait to 35 take route 3 and arrive at 60 - first
: Min:30: wait to 35 take route 3 and arrive at 60 - first
: Min:31: wait to 35 take route 3 and arrive at 60 - first
: Min:32: wait to 35 take route 3 and arrive at 60 - first
: Min:33: wait to 35 take route 3 and arrive at 60 - first
: Min:34: wait to 35 take route 3 and arrive at 60 - first
: Min:35: take route 3 and arrive at 60 - first
: Min:36: wait to 39 take route 2 and arrive at 74 - first
: Min:37: wait to 39 take route 2 and arrive at 74 - first
: Min:38: wait to 39 take route 2 and arrive at 74 - first
: Min:39: take route 2 and arrive at 74 - first
: Min:40: wait to 54 take route 2 and arrive at 89 * skip
: Min:41: wait to 54 take route 2 and arrive at 89 * skip
: Min:42: wait to 54 take route 2 and arrive at 89 * skip
: Min:43: wait to 54 take route 2 and arrive at 89 * skip
: Min:44: wait to 54 take route 2 and arrive at 89 * skip
: Min:45: wait to 54 take route 2 and arrive at 89 * skip
: Min:46: wait to 54 take route 2 and arrive at 89 * skip
: Min:47: wait to 54 take route 2 and arrive at 89 * skip
: Min:48: wait to 54 take route 2 and arrive at 89 * skip
: Min:49: wait to 54 take route 2 and arrive at 89 * skip
: Min:50: wait to 54 take route 2 and arrive at 89 * skip
: Min:51: wait to 54 take route 2 and arrive at 89 * skip
: Min:52: wait to 54 take route 2 and arrive at 89 - first
: Min:53: wait to 54 take route 2 and arrive at 89 - first
: Min:54: take route 2 and arrive at 89 - first
: Min:55 (no more departures this hour, and we skipped the first one this hour) * skip
: Min:56 (no more departures this hour, and we skipped the first one this hour) * skip
: Min:57 (no more departures this hour, and we skipped the first one this hour) * skip
: Min:58 (no more departures this hour, and we skipped the first one this hour) * skip
: Min:59 (no more departures this hour, and we skipped the first one this hour) * skip
[0, 1, 2, 3, 25, 26, 27, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 55, 56, 57,
58, 59]
And that's it.