Perl weekly challenge 103

Here are solutions to this weeks challenges from the Perl Weekly Challenge.

You can find my full code on Github

Task 1: Chinese zodiac

You are given a year $year.

The animal cycle: Rat, Ox, Tiger, Rabbit, Dragon, Snake, Horse, Goat, Monkey, Rooster, Dog, Pig.

The element cycle: Wood, Fire, Earth, Metal, Water.

Additionally there is a two year cycle between Yin & Yang

This challenge is a relatively simple challenge - and one perl is well suited:

sub year_name {
  return join q( ),
    qw( Yang   Yin                             )[  $_[0]    %  2 ],
    qw( Metal  Water   Wood   Fire  Earth      )[ ($_[0]/2) %  5 ],
    qw( Monkey Rooster Dog    Pig   Rat   Ox
        Tiger  Rabbit  Dragon Snake Horse Goat )[  $_[0]    % 12 ];

As the names cycle then we can use the modulus operator %. The the animal is a 12 year cycle, the Yin/Yang is a two year cycle (actually it's part of a 10 year cycle when you combine it with the elements).

The trick to make the code shorter is to realise you need to rotate the lists so they start with the values that year "0" would have - then it is just a simple modulus operator - rather than having to do a shift first $y%12 rather than the less clean ($x-8)%12.

TASK #2 › What’s playing?

A slightly longer task (to read), you have a track/episode list which contains the length of each track in milliseconds. This will loop continuously. Given a start time, and an end time - which track was the last you were looking at...

sub position {
  my ($start, $now, $filename ) = @_;
  my $tot_duration  = 0;
  open my $fh, q(<), $filename;
  $tot_duration += $_->[0]
    foreach my @episodes = map { [split m{,?"}] } <$fh>; #"fix colour
  close $fh;
  my $position =  1000 * ($now-$start) % $tot_duration;
  foreach( @episodes )  {
    return sprintf '%s @ %02d:%02d:%02d',
      int( $position/3600000 )     ,
      int( $position/  60000 ) % 60,
      int( $position/   1000 ) % 60  if $position < $_->[0];
    $position -= $_->[0];

The first part of the task was to slurp in csv file. Now we know the format as a file - so we cheat a little bit, we know we need to cut the string at ',"' and '". Rather than load in a CSV module to do this we can just use split to do that - we
do that in one line by using map {...} <..>.

OK - so I must admit a slight gold trick here... We can in the same statement add up the values of the lengths for each track (we will need this again shortly).

Rather than looping through until we get to the finish time, we can short cut the first loops by using the same % modulus sign above to work out where through the list we are in the last loop. Then it is a simple loop through the episodes to work out in which one we finish.

Finally it's just a case of using sprintf to format everything as required.

Leave a comment

About James Curtis-Smith

user-pic Perl developer for nearly 30 years now, mainly in maintenance scripts and web pages, using mod_perl. I also code a lot in PHP (20+yrs), and also extensive experience in HTML (27+yrs), Javascript(25+yrs), CSS (24+yrs) and SASS.