Can this Perl behavior be emulated with a switch / case or given / when?
I was wondering if anyone has any suggestions for improving the following code (if possible) so that it doesn't need to be repeated (my @a = $ time = ~ ...), perhaps using case / switch or given / when or some other idea what am I missing?
my $time = '12:59pm';
if( my @a = $time =~ m/^(\d\d?)(am|pm)$/ ) { tell_time( $a[0], 0, $a[1] ) }
if( my @a = $time =~ m/^(\d\d?):(\d\d)(am|pm)$/ ) { tell_time( @a ) }
if( my @a = $time =~ m/^(\d\d?):(\d\d)$/ ) { tell_time( @a ) }
sub tell_time
{
my $hour = shift;
my $minute = shift || '00';
my $ampm = shift || ( $hour > 12 ) ? 'pm' : 'am';
print "Hour: $hour, Minute: $minute, AMPM: $ampm\n";
}
I've tried playing with Switch and 5.10 if / when, but can't seem to do something like:
given( $time )
{
when( /^(\d\d?)(am|pm)$/ ) { tell_time( $_[0], 0, $_[1] ) }
when( /^(\d\d?):(\d\d)(am|pm)$/ ) { tell_time( @_ ) }
when( /^(\d\d?):(\d\d)$/ ) { tell_time( @_ ) }
}
This doesn't fly because @_ seems to store $ time.
Also note that I am more interested in the syntax of the problem than the problem the code solves. I am well aware that I can use Time :: ParseDate to define different parts of a string formatted as time or date.
a source to share
Your regex uses ()
to retrieve matches, but you don't need to store them in an array. If you want, they are stored in $1
, $2
, $3
, etc. Lookie:
given( $time )
{
when( /^(\d\d?)(am|pm)$/ ) { tell_time( $1, 0, $2 ) }
when( /^(\d\d?):(\d\d)(am|pm)$/ ) { tell_time( $1, $2, $3 ) }
when( /^(\d\d?):(\d\d)$/ ) { tell_time( $1, $2 ) }
}
I think what you want to do.
If you want to add to the syntax, I would write tell_time()
to just take the time as a string and ask the function to parse the result itself, rather than force the user of your code to parse it themselves. Alternatively, you can use this block given()
as the start of a new function that does exactly that - parses the temporary string and correctly passes it to tell_time()
. But that's just me. I don't know what you need your code to do, so will definitely go for it.
a source to share
Well, without using switch / case, I would just use one regex to capture all the options ...
#!/usr/bin/perl
tell_time ("12:59am"); # matches time format 1
tell_time ("2:59pm"); # matches time format 1
tell_time ("12am"); # matches time format 2
tell_time ("12:59"); # matches time format 3
tell_time ("14:59"); # matches time format 3
tell_time ("12:59:59am"); # produces no output, does not match any known time formats.
sub tell_time
{
my $timearg = shift;
# note: (?: ... ) creates a non-capturing group, which is not reflected in
# the returned array.
my ($hour , $minute, $ampm) = ( $timearg =~ m/^(\d\d?)(?::(\d\d?))?(am|pm)?$/ ) ;
# only continue if we captured all required fields (i.e. hour)
if($hour)
{
# set default values for optional fields (i.e. minute, ampm) if necessary
$minute ||= '00';
$ampm ||= ( $hour > 12 ) ? 'pm' : 'am';
print "Hour: $hour, Minute: $minute, AMPM: $ampm\n";
}
}
I can explain this further if needed, but I think if you can read perl it should be clear what it does ...
a source to share
Since you are using 5.10 you can use named capture in your regex:
#!/usr/bin/perl
use 5.010;
use strict;
use warnings;
my $hour24 = qr/(?<hour>[1-9]|1[0-9]|2[0-3])/;
my $hour12 = qr/(?<hour>[1-9]|1[0-2])/;
my $minute = qr/(?<minute>[0-5][0-9])/;
my $meridiem = qr/(?<meridiem>am|AM|pm|PM)/;
for my $time (qw(5pm 10am 5:59pm 10:00pm 5:00 22:00 24:00)) {
given($time) {
when(/ ^ $hour12 $meridiem $ /x) {
my $hour = $+{hour};
$hour += 12 if 'pm' eq lc $+{meridiem};
tell_time($hour, "00")
}
when(/ ^ $hour12 : $minute $meridiem $ /x) {
my $hour = $+{hour};
$hour += 12 if 'pm' eq lc $+{meridiem};
tell_time($hour, $+{minute})
}
when(/ ^ $hour24 : $minute $ /x) {
tell_time($+{hour}, $+{minute})
}
default {
say "bad time: $time";
}
}
}
sub tell_time {
my ($hour, $minute) = @_;
say "it is $hour:$minute";
}
a source to share
Chris Lutz has already covered the switch syntax using Perl 5.10. For Perl versions, you can use aliases for aliases:
for ($time) {
/^(\d\d?)(am|pm)$/ && do { tell_time( $1, 0, $2 ); last };
/^(\d\d?):(\d\d)(am|pm)$/ && do { tell_time( $1, $2, $3 ); last };
/^(\d\d?):(\d\d)$/ && do { tell_time( $1, $2 ); last };
}
a source to share
I'm not sure if the value / when is important here. I would just concatenate the possible patterns in one regex. Combined with the special variable% + and the defined or operator, we can make the code more concise.
#!/usr/bin/perl
use strict;
use warnings;
my @times = qw( 12:59pm 12 1pm 13:11 11 11pm);
my $hour_pat = '(?<hour>[0-9]{1,2})';
my $minute_pat = '(?<minute>[0-9]{2})';
my $ampm_pat = '(?<ampm>am|pm)';
my $re = qr{
\A
(?:$hour_pat : $minute_pat $ampm_pat)
|
(?:$hour_pat : $minute_pat)
|
(?:$hour_pat $ampm_pat)
|
(?:$hour_pat)
\z
}x;
for my $time ( @times ) {
if ( $time =~ $re ) {
tell_time( %+ );
}
}
sub tell_time {
my %time = @_;
printf( "Hour: %2.2d, Minute: %2.2d, AMPM: %s\n",
$time{hour},
$time{minute} // 0,
$time{ampm} // ( $time{hour} >= 12 ? 'pm' : 'am' ),
);
return;
}
a source to share
I am creating a radio button with a block label, for example:
my $time = '12:59pm';
SWITCH: {
$time =~ /^(\d\d?)(am|pm)$/ && do {
tell_time($1,0,$2);
last SWITCH;
};
$time =~ /^(\d\d?):(\d\d)(am|pm)$/ && do {
tell_time($1,$2,$3);
last SWITCH;
};
$time =~ /^(\d\d?):(\d\d)$/ && do {
tell_time($1,$2);
};
}
a source to share