aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--challenge-274/athanasius/perl/ch-1.pl195
-rw-r--r--challenge-274/athanasius/perl/ch-2.pl302
-rw-r--r--challenge-274/athanasius/raku/ch-1.raku191
-rw-r--r--challenge-274/athanasius/raku/ch-2.raku314
4 files changed, 1002 insertions, 0 deletions
diff --git a/challenge-274/athanasius/perl/ch-1.pl b/challenge-274/athanasius/perl/ch-1.pl
new file mode 100644
index 0000000000..b0e03ee9ec
--- /dev/null
+++ b/challenge-274/athanasius/perl/ch-1.pl
@@ -0,0 +1,195 @@
+#!perl
+
+################################################################################
+=comment
+
+Perl Weekly Challenge 274
+=========================
+
+TASK #1
+-------
+*Goat Latin*
+
+Submitted by: Mohammad Sajid Anwar
+
+You are given a sentence, $sentance.
+
+Write a script to convert the given sentence to Goat Latin, a made up language
+similar to Pig Latin.
+
+Rules for Goat Latin:
+
+ 1) If a word begins with a vowel ("a", "e", "i", "o", "u"), append
+ "ma" to the end of the word.
+ 2) If a word begins with consonant i.e. not a vowel, remove first
+ letter and append it to the end then add "ma".
+ 3) Add letter "a" to the end of first word in the sentence, "aa" to
+ the second word, etc etc.
+
+Example 1
+
+ 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"
+
+=cut
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2024 PerlMonk Athanasius #
+#--------------------------------------#
+
+#===============================================================================
+=comment
+
+Assumption
+----------
+Within a series of "word" (alphabetic) characters, a hyphen is taken to be a
+separator of distinct words.
+
+Interface
+---------
+1. If no command-line arguments are given, the test suite is run. Otherwise:
+2. The input sentence is entered as an unnamed (positional) argument on the
+ command-line.
+
+=cut
+#===============================================================================
+
+use v5.32.1; # Enables strictures
+use warnings;
+use Const::Fast;
+use Test::More;
+
+const my $USAGE => <<END;
+Usage:
+ perl $0 <sentence>
+ perl $0
+
+ <sentence> A sentence to be translated into Goat Latin
+END
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 274, Task #1: Goat Latin (Perl)\n\n";
+}
+
+#===============================================================================
+MAIN:
+#===============================================================================
+{
+ my $argc = scalar @ARGV;
+
+ if ($argc == 0)
+ {
+ run_tests();
+ }
+ elsif ($argc == 1)
+ {
+ my $sentence = $ARGV[ 0 ];
+
+ print qq[Input: \$sentence = "$sentence"\n];
+
+ my $translation = translate( $sentence );
+
+ print qq[Output: "$translation"\n];
+ }
+ else
+ {
+ error( "Expected 1 or 0 command-line arguments, found $argc" );
+ }
+}
+
+#-------------------------------------------------------------------------------
+sub translate
+#-------------------------------------------------------------------------------
+{
+ my ($sentence) = @_;
+ my $translation = '';
+ my $count = 1;
+
+ for my $chunk (split / \b /x, $sentence) # Split on word boundaries
+ {
+ if ($chunk =~ / \w /x) # (1) The chunk is a word that...
+ {
+ if ($chunk =~ / ^ [aeiou] /ix) # (1a) ...begins with a vowel
+ {
+ $translation .= $chunk;
+ }
+ else # (1b) ...begins with a consonant
+ {
+ $translation .= substr( $chunk, 1 ) . substr( $chunk, 0, 1 );
+ }
+
+ $translation .= 'ma' . 'a' x $count++; # Append goatish suffix
+ }
+ else # (2) The chunk is space(s) and/or punctuation
+ {
+ $translation .= $chunk;
+ }
+ }
+
+ return $translation;
+}
+
+#-------------------------------------------------------------------------------
+sub run_tests
+#-------------------------------------------------------------------------------
+{
+ print "Running the test suite\n";
+
+ while (my $line = <DATA>)
+ {
+ chomp $line;
+
+ while ($line =~ / \\ $ /x)
+ {
+ chop $line;
+
+ $line .= <DATA> =~ s/ ^ \s+ //rx;
+ }
+
+ my ($test_name, $sentence, $expected) = split / \| /x, $line;
+
+ for ($test_name, $sentence, $expected)
+ {
+ s/ ^ \s+ //x;
+ s/ \s+ $ //x;
+ }
+
+ my $translation = translate( $sentence );
+
+ is $translation, $expected, $test_name;
+ }
+
+ done_testing;
+}
+
+#-------------------------------------------------------------------------------
+sub error
+#-------------------------------------------------------------------------------
+{
+ my ($message) = @_;
+
+ die "ERROR: $message\n$USAGE";
+}
+
+################################################################################
+
+__DATA__
+Example 1|I love Perl |Imaa ovelmaaa erlPmaaaa
+Example 2|Perl and Raku are friends|erlPmaa andmaaa akuRmaaaa aremaaaaa \
+ riendsfmaaaaaa
+Example 3|The Weekly Challenge |heTmaa eeklyWmaaa hallengeCmaaaa
diff --git a/challenge-274/athanasius/perl/ch-2.pl b/challenge-274/athanasius/perl/ch-2.pl
new file mode 100644
index 0000000000..0d32dff1b7
--- /dev/null
+++ b/challenge-274/athanasius/perl/ch-2.pl
@@ -0,0 +1,302 @@
+#!perl
+
+################################################################################
+=comment
+
+Perl Weekly Challenge 274
+=========================
+
+TASK #2
+-------
+*Bus Route*
+
+Submitted by: Peter Campbell Smith
+
+Several bus routes start from a bus stop near my home, and go to the same stop
+in town. They each run to a set timetable, but they take different times to get
+into town.
+
+Write a script to find the times - if any - I should let one bus leave and catch
+a strictly later one in order to get into town strictly sooner.
+
+An input timetable consists of the service interval, the offset within the hour,
+and the duration of the trip.
+
+Example 1
+
+ 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 ]
+
+=cut
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2024 PerlMonk Athanasius #
+#--------------------------------------#
+
+#===============================================================================
+=comment
+
+Interface
+---------
+1. If no command-line arguments are given, the test suite is run. Otherwise:
+2. One or more "timetable strings" are entered as unnamed arguments on the
+ command-line. Each timetable string contains a service-interval, an hour-
+ offset, and a trip-duration, separated by whitespace (with optional commas).
+
+=cut
+#===============================================================================
+
+use v5.32.1; # Enables strictures
+use warnings;
+use Const::Fast;
+use Test::More;
+
+const my $USAGE => <<END;
+Usage:
+ perl $0 [<timetables> ...]
+ perl $0
+
+ [<timetables> ...] Timetable-strings, e.g., "12 11 41" "15 5 35"
+END
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ $| = 1;
+ print "\nChallenge 274, Task #2: Bus Route (Perl)\n\n";
+}
+
+#-------------------------------------------------------------------------------
+package Route
+#-------------------------------------------------------------------------------
+{
+ use Class::Accessor 'antlers';
+
+ has interval => ( is => 'ro', isa => 'Num' );
+ has offset => ( is => 'ro', isa => 'Num' );
+ has duration => ( is => 'ro', isa => 'Num' );
+
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ sub next_start
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ {
+ my ($self, $current_time) = @_;
+
+ my $time = $self->offset;
+ $time += $self->interval while $time < $current_time;
+
+ return $time;
+ }
+
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ sub next_arrival
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ {
+ my ($self, $leave_time) = @_;
+
+ return $leave_time + $self->duration;
+ }
+}
+
+#===============================================================================
+MAIN:
+#===============================================================================
+{
+ if (scalar @ARGV == 0)
+ {
+ run_tests();
+ }
+ else
+ {
+ my $routes = parse_timetables( \@ARGV );
+
+ printf "Input: [ %s ]\n", join ', ', map { sprintf '[%d, %d, %d]',
+ $_->interval, $_->offset, $_->duration } @$routes;
+
+ my $leave_times = find_leave_times( $routes );
+
+ printf "Output: [ %s ]\n", join ', ', @$leave_times;
+ }
+}
+
+#-------------------------------------------------------------------------------
+sub find_leave_times
+#-------------------------------------------------------------------------------
+{
+ my ($routes) = @_;
+ my @leave_times;
+
+ for my $current_time (0 .. 59)
+ {
+ my ($min_route, $min_start, $min_arrival) =
+ find_min_start( $routes, $current_time );
+
+ for my $i (0 .. $#$routes)
+ {
+ next if $i == $min_route;
+
+ my $start = $routes->[ $i ]->next_start( $current_time );
+
+ if ($start > $min_start)
+ {
+ my $arrival = $routes->[ $i ]->next_arrival( $start );
+
+ if ($arrival < $min_arrival)
+ {
+ push @leave_times, $current_time;
+ last;
+ }
+ }
+ }
+ }
+
+ return \@leave_times;
+}
+
+#-------------------------------------------------------------------------------
+sub find_min_start
+#-------------------------------------------------------------------------------
+{
+ my ($routes, $current_time) = @_;
+
+ my $min_route = 0;
+ my $min_start = $routes->[ 0 ]->next_start( $current_time );
+ my $min_arrival = $routes->[ 0 ]->next_arrival( $min_start );
+
+ for my $i (1 .. $#$routes)
+ {
+ my $start_time = $routes->[ $i ]->next_start( $current_time );
+
+ if ($start_time < $min_start)
+ {
+ $min_route = $i;
+ $min_start = $start_time;
+ $min_arrival = $routes->[ $i ]->next_arrival( $min_start );
+ }
+ elsif ($start_time == $min_start)
+ {
+ my $arrival = $routes->[ $i ]->next_arrival( $min_start );
+
+ if ($arrival < $min_arrival)
+ {
+ $min_route = $i;
+ $min_start = $start_time;
+ $min_arrival = $arrival;
+ }
+ }
+ }
+
+ return ($min_route, $min_start, $min_arrival);
+}
+
+#-------------------------------------------------------------------------------
+sub parse_timetables
+#-------------------------------------------------------------------------------
+{
+ my ($timetables) = @_;
+ my @routes;
+
+ for my $timetable (@$timetables)
+ {
+ $timetable =~ / ^ \s* (\d+) \,? \s+ (\d+) \,? \s+ (\d+) \s* $ /x
+ or error( qq[Invalid input timetable "$timetable"] );
+
+ my ($interval, $offset, $duration) = @{ ^CAPTURE };
+
+ 0 < $interval or error( qq["$interval" is not a valid interval] );
+ 0 <= $offset < 60 or error( qq["$offset" is not a valid offset] );
+
+ push @routes, Route->new( { interval => $interval,
+ offset => $offset,
+ duration => $duration } );
+ }
+
+ return \@routes;
+}
+
+#-------------------------------------------------------------------------------
+sub run_tests
+#-------------------------------------------------------------------------------
+{
+ print "Running the test suite\n";
+
+ while (my $line = <DATA>)
+ {
+ chomp $line;
+
+ my ($test_name, $timetables_str, $expected_str) = split / \| /x, $line;
+
+ for ($test_name, $timetables_str, $expected_str)
+ {
+ s/ ^ \s+ //x;
+ s/ \s+ $ //x;
+ }
+
+ my @timetables = split / \; \s+ /x, $timetables_str;
+ my $routes = parse_timetables( \@timetables );
+ my $leave_times = find_leave_times( $routes );
+ my $expected = parse_expected( $expected_str );
+
+ is_deeply $leave_times, $expected, $test_name;
+ }
+
+ done_testing;
+}
+
+#-------------------------------------------------------------------------------
+sub parse_expected
+#-------------------------------------------------------------------------------
+{
+ my ($expected_str) = @_;
+ my @expected;
+
+ for my $s (split / \s+ /x, $expected_str)
+ {
+ if ($s =~ / ^ (\d+) \- (\d+) $ /x)
+ {
+ push @expected, $1 .. $2;
+ }
+ else
+ {
+ push @expected, $s;
+ }
+ }
+
+ return \@expected;
+}
+
+#-------------------------------------------------------------------------------
+sub error
+#-------------------------------------------------------------------------------
+{
+ my ($message) = @_;
+
+ die "ERROR: $message\n$USAGE";
+}
+
+################################################################################
+
+__DATA__
+Example 1 |12 11 41; 15 5 35 |36-47
+Example 2 |12 3 41; 15 9 35; 30 5 25 |0-3 25-27 40-51 55-59
+One route 1|12 3 41 |
+One route 2|12 3 41; 12 3 41 |
+Longer |12 3 42; 12 3 41 |
+Example 1a |12 11 41; 15 5 35; 15 5 36 |36-47
+Example 2a |12 3 41; 15 9 35; 30 5 25; 15 9 36|0-3 25-27 40-51 55-59
diff --git a/challenge-274/athanasius/raku/ch-1.raku b/challenge-274/athanasius/raku/ch-1.raku
new file mode 100644
index 0000000000..9f49ee669d
--- /dev/null
+++ b/challenge-274/athanasius/raku/ch-1.raku
@@ -0,0 +1,191 @@
+use v6d;
+
+################################################################################
+=begin comment
+
+Perl Weekly Challenge 274
+=========================
+
+TASK #1
+-------
+*Goat Latin*
+
+Submitted by: Mohammad Sajid Anwar
+
+You are given a sentence, $sentance.
+
+Write a script to convert the given sentence to Goat Latin, a made up language
+similar to Pig Latin.
+
+Rules for Goat Latin:
+
+ 1) If a word begins with a vowel ("a", "e", "i", "o", "u"), append
+ "ma" to the end of the word.
+ 2) If a word begins with consonant i.e. not a vowel, remove first
+ letter and append it to the end then add "ma".
+ 3) Add letter "a" to the end of first word in the sentence, "aa" to
+ the second word, etc etc.
+
+Example 1
+
+ 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"
+
+=end comment
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2024 PerlMonk Athanasius #
+#--------------------------------------#
+
+#===============================================================================
+=begin comment
+
+Assumption
+----------
+Within a series of "word" (alphabetic) characters, a hyphen is taken to be a
+separator of distinct words.
+
+Interface
+---------
+1. If no command-line arguments are given, the test suite is run. Otherwise:
+2. The input sentence is entered as an unnamed (positional) argument on the
+ command-line.
+
+=end comment
+#===============================================================================
+
+use Test;
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ "\nChallenge 274, Task #1: Goat Latin (Raku)\n".put;
+}
+
+#===============================================================================
+multi sub MAIN
+(
+ Str:D $sentence #= A sentence to be translated into Goat Latin
+)
+#===============================================================================
+{
+ qq[Input: \$sentence = "$sentence"].put;
+
+ my Str $translation = translate( $sentence );
+
+ qq[Output: "$translation"].put;
+}
+
+#===============================================================================
+multi sub MAIN() # No input: run the test suite
+#===============================================================================
+{
+ run-tests();
+}
+
+#-------------------------------------------------------------------------------
+sub translate( Str:D $sentence --> Str:D )
+#-------------------------------------------------------------------------------
+{
+ my Str $translation = '';
+ my UInt $count = 1;
+
+ for $sentence.split: / <|w> /, :skip-empty -> Str $chunk # Split on word
+ { # boundaries
+ if $chunk ~~ / \w / # (1) The chunk is a word that...
+ {
+ if $chunk ~~ m:i/ ^ <[aeiou]> / # (1a) ...begins with a vowel
+ {
+ $translation ~= $chunk;
+ }
+ else # (1b) ...begins with a consonant
+ {
+ $translation ~= $chunk.substr( 1 ) ~ $chunk.substr( 0, 1 );
+ }
+
+ $translation ~= 'ma' ~ 'a' x $count++; # Append goatish suffix
+ }
+ else # (2) The chunk is space(s) and/or punctuation
+ {
+ $translation ~= $chunk;
+ }
+ }
+
+ return $translation;
+}
+
+#-------------------------------------------------------------------------------
+sub run-tests()
+#-------------------------------------------------------------------------------
+{
+ 'Running the test suite'.put;
+
+ for test-data.lines -> Str $line
+ {
+ my Str ($test-name, $sentence, $expected) = $line.split: / \| /;
+
+ for $test-name, $sentence, $expected
+ {
+ s/ ^ \s+ //;
+ s/ \s+ $ //;
+ }
+
+ my Str $translation = translate( $sentence );
+
+ is $translation, $expected, $test-name;
+ }
+
+ done-testing;
+}
+
+#-------------------------------------------------------------------------------
+sub error( Str:D $message )
+#-------------------------------------------------------------------------------
+{
+ "ERROR: $message".put;
+
+ USAGE();
+
+ exit 0;
+}
+
+#-------------------------------------------------------------------------------
+sub USAGE()
+#-------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s:g/ ($*PROGRAM-NAME) /raku $0/;
+
+ $usage.put;
+}
+
+#-------------------------------------------------------------------------------
+sub test-data( --> Str:D )
+#-------------------------------------------------------------------------------
+{
+ my Str $data = q:to/END/;
+ Example 1|I love Perl |Imaa ovelmaaa erlPmaaaa
+ Example 2|Perl and Raku are friends|erlPmaa andmaaa akuRmaaaa aremaaaaa\
+ riendsfmaaaaaa
+ Example 3|The Weekly Challenge |heTmaa eeklyWmaaa hallengeCmaaaa
+ END
+
+ $data ~~ s:g/ \\ \n //; # Concatenate backslashed lines
+
+ return $data;
+}
+
+################################################################################
diff --git a/challenge-274/athanasius/raku/ch-2.raku b/challenge-274/athanasius/raku/ch-2.raku
new file mode 100644
index 0000000000..0e4bd64879
--- /dev/null
+++ b/challenge-274/athanasius/raku/ch-2.raku
@@ -0,0 +1,314 @@
+use v6d;
+
+################################################################################
+=begin comment
+
+Perl Weekly Challenge 274
+=========================
+
+TASK #2
+-------
+*Bus Route*
+
+Submitted by: Peter Campbell Smith
+
+Several bus routes start from a bus stop near my home, and go to the same stop
+in town. They each run to a set timetable, but they take different times to get
+into town.
+
+Write a script to find the times - if any - I should let one bus leave and catch
+a strictly later one in order to get into town strictly sooner.
+
+An input timetable consists of the service interval, the offset within the hour,
+and the duration of the trip.
+
+Example 1
+
+ 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 ]
+
+=end comment
+################################################################################
+
+#--------------------------------------#
+# Copyright © 2024 PerlMonk Athanasius #
+#--------------------------------------#
+
+#===============================================================================
+=begin comment
+
+Interface
+---------
+1. If no command-line arguments are given, the test suite is run. Otherwise:
+2. One or more "timetable strings" are entered as unnamed arguments on the
+ command-line. Each timetable string contains a service-interval, an hour-
+ offset, and a trip-duration, separated by whitespace (with optional commas).
+
+=end comment
+#===============================================================================
+
+use Test;
+
+subset Interval of Int where 0 < *;
+subset Offset of Int where 0 <= * < 60;
+
+#-------------------------------------------------------------------------------
+BEGIN
+#-------------------------------------------------------------------------------
+{
+ "\nChallenge 274, Task #2: Bus Route (Raku)\n".put;
+}
+
+#-------------------------------------------------------------------------------
+class Route
+#-------------------------------------------------------------------------------
+{
+ # All measurements in minutes
+ has Interval $.interval is required; # Service interval
+ has Offset $.offset is required; # Offset within the hour
+ has UInt $.duration is required; # Duration of the trip
+
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ method next-start( Offset:D $current-time --> UInt:D )
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ {
+ my UInt $time = $!offset;
+ $time += $!interval while $time < $current-time;
+
+ return $time;
+ }
+
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ method next-arrival( UInt:D $leave-time --> UInt:D )
+ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ {
+ return $leave-time + $!duration;
+ }
+}
+
+#===============================================================================
+multi sub MAIN
+(
+ #| Timetable-strings, e.g., "12 11 41" "15 5 35"
+
+ *@timetables where { .elems > 0 }
+)
+#===============================================================================
+{
+ my Route @routes = parse-timetables( @timetables );
+
+ "Input: [ %s ]\n".printf:
+ @routes.map(
+ { '[%d, %d, %d]'.sprintf: .interval, .offset, .duration }
+
+ ).join: ', ';
+
+ my Offset @leave-times = find-leave-times( @routes );
+
+ "Output: [ %s ]\n".printf: @leave-times.join: ', ';
+}
+
+#===============================================================================
+multi sub MAIN() # No input: run the test suite
+#===============================================================================
+{
+ run-tests();
+}
+
+#-------------------------------------------------------------------------------
+sub find-leave-times( List:D[Route:D] $routes --> List:D[Offset:D] )
+#-------------------------------------------------------------------------------
+{
+ my Offset @leave-times;
+
+ for 0 ..^ 60 -> Offset $current-time
+ {
+ my UInt ($min-route, $min-start, $min-arrival) =
+ find-min-start( $routes, $current-time );
+
+ for 0 .. $routes.end -> UInt $i
+ {
+ next if $i == $min-route;
+
+ my UInt $start = $routes[ $i ].next-start( $current-time );
+
+ if $start > $min-start
+ {
+ my UInt $arrival = $routes[ $i ].next-arrival( $start );
+
+ if $arrival < $min-arrival
+ {
+ @leave-times.push: $current-time;
+ last;
+ }
+ }
+ }
+ }
+
+ return @leave-times;
+}
+
+#-------------------------------------------------------------------------------
+sub find-min-start
+(
+ List:D[Route:D] $routes,
+ Offset:D $current-time
+--> List:D[UInt:D]
+)
+#-------------------------------------------------------------------------------
+{
+ my UInt $min-route = 0;
+ my UInt $min-start = $routes[ 0 ].next-start( $current-time );
+ my UInt $min-arrival = $routes[ 0 ].next-arrival( $min-start );
+
+ for 1 .. $routes.end -> UInt $i
+ {
+ my UInt $start-time = $routes[ $i ].next-start( $current-time );
+
+ if $start-time < $min-start
+ {
+ $min-route = $i;
+ $min-start = $start-time;
+ $min-arrival = $routes[ $i ].next-arrival( $min-start );
+ }
+ elsif $start-time == $min-start
+ {
+ my UInt $arrival = $routes[ $i ].next-arrival( $min-start );
+
+ if $arrival < $min-arrival
+ {
+ $min-route = $i;
+ $min-start = $start-time;
+ $min-arrival = $arrival;
+ }
+ }
+ }
+
+ return $min-route, $min-start, $min-arrival;
+}
+
+#-------------------------------------------------------------------------------
+sub parse-timetables( List:D[Str:D] $timetables --> List:D[Route:D] )
+#-------------------------------------------------------------------------------
+{
+ my Route @routes;
+
+ for @$timetables -> Str $timetable
+ {
+ $timetable ~~ / ^ \s* (\d+) \,? \s+ (\d+) \,? \s+ (\d+) \s* $ /
+ or error( qq[Invalid input timetable "$timetable"] );
+
+ my Int ($interval, $offset, $duration) = $/.map: { .Int };
+
+ 0 < $interval or error( qq["$interval" is not a valid interval] );
+ 0 <= $offset < 60 or error( qq["$offset" is not a valid offset] );
+
+ @routes.push: Route.new: :$interval, :$offset, :$duration;
+ }
+
+ return @routes;
+}
+
+#-------------------------------------------------------------------------------
+sub run-tests()
+#-------------------------------------------------------------------------------
+{
+ 'Running the test suite'.put;
+
+ for test-data.lines -> Str $line
+ {
+ my Str ($test-name, $timetables-str, $expected-str) =
+ $line.split: / \| /;
+
+ for $test-name, $timetables-str, $expected-str
+ {
+ s/ ^ \s+ //;
+ s/ \s+ $ //;
+ }
+
+ my Str @timetables = $timetables-str.split: / \; \s+ /;
+ my Route @routes = parse-timetables( @timetables );
+ my Offset @leave-times = find-leave-times( @routes );
+ my Offset @expected = parse-expected( $expected-str );
+
+ is-deeply @leave-times, @expected, $test-name;
+ }
+
+ done-testing;
+}
+
+#-------------------------------------------------------------------------------
+sub parse-expected( Str:D $expected-str --> List:D[Offset:D] )
+#-------------------------------------------------------------------------------
+{
+ my Offset @expected;
+
+ for $expected-str.split( / \s+ /, :skip-empty ) -> Str $s
+ {
+ if $s ~~ / ^ (\d+) \- (\d+) $ /
+ {
+ my UInt ($begin, $end) = $/.map: { .Int };
+
+ @expected.push: |($begin .. $end);
+ }
+ else
+ {
+ @expected.push: $s.Int;
+ }
+ }
+
+ return @expected;
+}
+
+#-------------------------------------------------------------------------------
+sub error( Str:D $message )
+#-------------------------------------------------------------------------------
+{
+ "ERROR: $message".put;
+
+ USAGE();
+
+ exit 0;
+}
+
+#-------------------------------------------------------------------------------
+sub USAGE()
+#-------------------------------------------------------------------------------
+{
+ my Str $usage = $*USAGE;
+
+ $usage ~~ s:g/ ($*PROGRAM-NAME) /raku $0/;
+
+ $usage.put;
+}
+
+#-------------------------------------------------------------------------------
+sub test-data( --> Str:D )
+#-------------------------------------------------------------------------------
+{
+ return q:to/END/;
+ Example 1 |12 11 41; 15 5 35 |36-47
+ Example 2 |12 3 41; 15 9 35; 30 5 25 |0-3 25-27 40-51 55-59
+ One route 1|12 3 41 |
+ One route 2|12 3 41; 12 3 41 |
+ Longer |12 3 42; 12 3 41 |
+ Example 1a |12 11 41; 15 5 35; 15 5 36 |36-47
+ Example 2a |12 3 41; 15 9 35; 30 5 25; 15 9 36|0-3 25-27 40-51 55-59
+ END
+}
+
+################################################################################