I’ve been doing the Perl Weekly
Challenges. The
latest
involved lots of modulus arithmetic. (Note that this is open until 14
March 2021.)
TASK #1 › Chinese Zodiac
You are given a year $year
.
Write a script to determine the Chinese Zodiac for the given year
$year
. Please check out
wikipage for more
information about it.
I accidentally used the Vietnamese zodiac instead. Works the same way
except that Water Buffalo replaces Ox and Cat replaces Rabbit.
What this comes down to is most but not all of the sexagenary cycle,
combining the 12-year animals with the 10-year elements, but ignoring
the subdivisions of the elements into single-year yin and yang
aspects. So if I'm going to be using AD-year modulo 60, I might as
well choose a starting point for each cycle where (AD-year modulo 60)
is zero.
As a bonus feature, you can enter a BCE date as a negative value.
sub cz {
my $yy=shift;
my $y=$yy;
Convert BCE to Cassini's astronomical year numbering (i.e. the way it
should have been done in the first place, with a year zero).
if ($y<0) {
$y++;
}
Reduce to a number between 0 and 59.
$y%=60;
while ($y<0) {
$y+=60;
}
Then do the lookups. Element starts at Metal and increments each two
years on a ten-year cycle, animal starts at Monkey and increments each
year on a twelve-year cycle. In astrological use, the cycles start
with Wood and Rat.
return join(' ',
[qw(Metal Water Wood Fire Earth)]->[int($y/2)%5],
[qw(Monkey Rooster Dog Pig Rat),'Water Buffalo',qw(Tiger Cat Dragon Snake Horse Goat)]->[$y%12]);
}
The other languages look very similar, except that in Rust I need to
convert the year explicitly to a usize
before using it as a
subscript.
TASK #2 › What’s playing?
Working from home, you decided that on occasion you wanted some
background noise while working. You threw together a network
streamer to continuously loop through the files and launched it in a
tmux (or screen) session, giving it a directory tree of files to
play. During the day, you connected an audio player to the stream,
listening through the workday, closing it when done.
For weeks you connect to the stream daily, slowly noticing a gradual
drift of the media. After several weeks, you take vacation. When you
return, you are pleasantly surprised to find the streamer still
running. Before connecting, however, if you consider the puzzle of
determining which track is playing.
After looking at a few modules to read info regarding the media, a
quick bit of coding gave you a file list. The file list is in a
simple CSV format, each line containing two fields: the first the
number of milliseconds in length, the latter the media’s title
[example elided]
For this script, you can assume to be provided the following information:
* the value of $^T ($BASETIME) of the streamer script,
* the value of time(), and
* a CSV file containing the media to play consisting of the
length in milliseconds and an identifier for the media (title,
filename, or other).
Write a program to output which file is currently playing. For
purposes of this script, you may assume gapless playback, and format
the output as you see fit.
Optional: Also display the current position in the media as a time-like value.
I do like a bit of narrative around a problem rather than "perform
this obscure evolution for no obvious reason". So the basic algorithm
here is:
- find the time between playlist start and now
- find the duration of the playlist
- take the remainder of the former divided by the latter, and use that
as a time index into the playlist
We also have to read a CSV file. In Perl that's the job of the
venerable but excellent Text::CSV_XS
, which can slurp the file into
an array of arrays.
sub wp {
my $ts=shift;
my $tn=shift;
my $csvfile=shift;
Determine elapsed time from start of play (ms)
my $td=($tn-$ts)*1000;
my $aoa=csv(in => $csvfile);
Determine duration of playlist (ms)
my $tp=sum(map {$_->[0]} @{$aoa});
Determine time since most recent start of playlist (ms)
$td %= $tp;
For each track, check whether our current time offset lies inside it.
If so, format that offset and return the output. Otherwise, reduce the
offset by the track length and continue.
foreach my $t (@{$aoa}) {
if ($td < $t->[0]) {
$td=int($td/1000);
my $h=int($td/3600);
my $m=int($td/60) % 60;
my $s=$td % 60;
return sprintf('%02d:%02d:%02d %s',$h,$m,$s,$t->[1]);
} else {
$td-=$t->[0];
}
}
}
Raku has a CSV-reading module, but I didn't want the faff of
installing it, and I thought grammars looked fun. Maybe they are, but
they seem to come back to regexps in the end while I'd been hoping for
a more solid parser infrastructure, so I just did it the old-fashioned
way. Python and Ruby have CSV readers in the language core, and Rust
has a module. Rust, again, got a custom struct
; it just feels like a
more Rusty way of doing things than packing dissimilar items into a
vector, even though that would also work.
Full code on
github.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.