This is my response to the Perl Weekly Challenge #103.
$year
.
Write a script to determine the Chinese Zodiac
for the given year $year
.
Please check out wikipage for more
information about it.
Input: 2017
Output: Fire Rooster
Example 2:
Input: 1938
Output: Earth Tiger
Note that the element cycle has 10 values, and not 5 as shown in the challenge, as each value is duplicated. (First as a «Yang» version, followed by a «Yin» version.) I'll ignore the «Yin and Yang», so that we get exactly the same output as requested in the challenge.
The base year (as defined in the wikipedia article) is 1924 (or 1984). All we have to do, is computing the offset (difference) from the year we are given, and count our way. Modulo using the number of elements in the cycles does just that.
File: chinese-zodiac
#! /usr/bin/env raku
unit sub MAIN (Int $year); # [1]
my @animals = <Rat Ox Tiger Rabbit Dragon Snake Horse Goat \ # [2]
Monkey Rooster Dog Pig>;
my @elements = <Wood Wood Fire Fire Earth Earth Metal Metal \ # [3]
Water Water>;
my $animals = @animals.elems; # [4]
my $elements = @elements.elems; # [5]
my $base-year = 1924; # [6]
my $year-diff = $year - $base-year; # [7]
say "{ @elements[ $year-diff % $elements] } \ # [8]
{ @animals[ $year-diff % $animals] }"; # [9]
[1] The year (as an integer). The wikipedia page shows the years 1924 to 2043, but we can extrapolate the sequence in both directions.
[2] The sequence of animals.
[3] The sequence of elements. They come as pairs, according to the wikipedia article, so the challenge text is misleading here.
[4] The number of animals (i.e. the number of values before we start from the beginning again).
[5] Ditto for the elements.
[6] The base year (1924), which have the first element in each list. I could just as well have chosen 1984.
[7] The current year is so many years off from the base year.
[8] The modulo operator %
gives us the index to the
elements array.
[9] As above, for the animals.
See
docs.raku.org/routine/% for more information about the modulo operator %
.
Running it:
$ ./chinese-zodiac 2017
Fire Rooster
$ ./chinese-zodiac 1938
Earth Tiger
$ ./chinese-zodiac 1939
Earth Rabbit
$ ./chinese-zodiac 1940
Metal Dragon
Looking good...
Except that we should perhaps show the «Yin» and «Yang» prefixes for the elements, as given by the wikipedia article.
And show off our Unicode knowledge by supporting the Chinese characters as well.
File: chinese-zodiac-c
#! /usr/bin/env raku
unit sub MAIN (Int $year, :c(:$chinese)); # [1]
my @animals = <Rat Ox Tiger Rabbit Dragon Snake Horse Goat Monkey Rooster Dog Pig>;
my @elements = <Yang/Wood Yin/Wood Yang/Fire Yin/Fire Yang/Earth Yin/Earth \
Yang/Metal Yin/Metal Yang/Water Yin/Water>; # [2]
my %chinese = ( Rat => '子', Ox => '丑', Tiger => '寅',
Rabbit => '卯', Dragon => '辰', Snake => '巳',
Horse => '午', Goat => '未', Monkey => '申',
Rooster => '酉', Dog => '戌', Pig => '亥',
'Yang/Wood' => '甲', 'Yin/Wood' => '乙', 'Yang/Fire' => '丙',
'Yin/Fire' => '丁', 'Yang/Earth' => '戊', 'Yin/Earth' => '己',
'Yang/Metal' => '庚', 'Yin/Metal' => '辛', 'Yang/Water' => '壬',
'Yin/Water' => '癸'); # [3]
my $animals = @animals.elems;
my $elements = @elements.elems;
my $base-year = 1924;
my $year-diff = $year - $base-year;
say $chinese # [4]
?? "{ %chinese{ @elements[ $year-diff % $elements] } } \
{ %chinese{ @animals[ $year-diff % $animals] } }"
!! "{ @elements[ $year-diff % $elements] } { @animals[ $year-diff % $animals] }";
[1] Use the «-c» or «--chinese» command line option if you want the program to print chinese characters.
[2] This time, prefixed with «Yin» and «Yang».
[3] The mapping between the names and the chinese characters.
[4] Print the chinese characters (the ??
line) if requested, and the
names if not (the !!
line).
Running it:
$ ./chinese-zodiac-c 2017
Yin/Fire Rooster
$ ./chinese-zodiac-c 1938
Yang/Earth Tiger
$ ./chinese-zodiac-c 1939
Yin/Earth Rabbit
$ ./chinese-zodiac-c 1940
Yang/Metal Dragon
$ ./chinese-zodiac-c -c 2017
丁 酉
$ ./chinese-zodiac-c -c 1938
戊 寅
$ ./chinese-zodiac-c -c 1939
己 卯
$ ./chinese-zodiac-c -c 1940
庚 辰
Note that we did not have to quote the keys (in [3]) for the animals, as they use letters only. Digits would also work out here, but the first character has to be a letter. (And by letter, Raku allows anything that is a Unicode letter.)
1709363,"Les Miserables Episode 1: The Bishop (broadcast date: 1937-07-23)"
1723781,"Les Miserables Episode 2: Javert (broadcast date: 1937-07-30)"
1723781,"Les Miserables Episode 3: The Trial (broadcast date: 1937-08-06)"
1678356,"Les Miserables Episode 4: Cosette (broadcast date: 1937-08-13)"
1646043,"Les Miserables Episode 5: The Grave (broadcast date: 1937-08-20)"
1714640,"Les Miserables Episode 6: The Barricade (broadcast date: 1937-08-27)"
1714640,"Les Miserables Episode 7: Conclusion (broadcast date: 1937-09-03)"
For this script, you can assume to be provided the following information:
Input: 3 command line parameters: start time, current time, file name
# starttime
1606134123
# currenttime
1614591276
# filelist.csv
Output:
"Les Miserables Episode 1: The Bishop (broadcast date: 1937-07-23)"
00:10:24
File: whats-playing
#! /usr/bin/env raku
unit sub MAIN (Int $start, # [1]
Int $current where $current > $start, # [2]
$file where $file.IO.f && $file.IO.r, # [3]
:v(:$verbose));
my @duration; # [4]
my @title; # [5]
my $start-ms = $start * 1000; # [6]
my $current-ms = $current * 1000; # [7]
for $file.IO.lines -> $line # [8]
{
my ($time, $title) = $line.split(",", 2); # [9]
@duration.push: $time; # [10]
@title.push: $title; # [10a]
}
my $time-ms = $start-ms; # [11]
my $title = 0; # [11a]
LOOP: loop # [12]
{
for ^@duration.elems -> $index # [13]
{
$title = $index; # [14]
last LOOP if $time-ms + @duration[$index] > $current-ms; # [15]
$time-ms += @duration[$index]; # [16]
say ": $time-ms starting @title[$index]" if $verbose;
}
}
say @title[$title]; # [17]
my $d = ($current-ms - $time-ms) / 1000; # [18]
my ($sec, $min, $hour) = $d.polymod(60,60); # [19]
say "{ $hour.fmt('%02d') }:{ $min.fmt('%02d') }:{ $sec.fmt('%02d') }"; # [20]
[1] The start time (in seconds since the epoch, 1. january 1970).
[2] The current time (also in seconds sine the epoch).
[3] The CSV file. Ensure that it is a file (IO.f
) and that the program
can read it (IO.r
).
[4] The duration fields from the CSV file goes here.
[5] Ditto for the text (title). The index is the key for both arrays.
[6] The start time in milliseconds, as the CSV file uses milliseconds.
[7] Ditto for the current time.
[8] For each line in the CSV file:
[9] Split the line on the first comma, in two parts
only (the second argument to split
).
[10] Add the duration part to the duartion array, and the title (description) to the title array [10a].
[11] Where we are in time as we play the movies. (The current movie is in [11a].)
[12] An eternal loop. Note the label, so that we can terminate the outer loop later on.
[13] Iterate over the movies, by their index.
[14] We are watching the current movie.
[15] Terminate the outer loop (the label «LOOP») if the end (timewise) of playing the current movie lies in the future.
[16] The last
(in [15]) did not kick in, so we can safely add the
current movie to the time, before the next iteration has a go at yet another
movie.
[17] We are done (have reached the current time). Print the current movie.
[18] The number of seconds into the current movie, converted from milliseconds.
[19] Convert the number of seconds to hours, minutes and seconds
with polymod
.
[20] Print the hours, minutes and seconds, with two digits each (zero padded).
See
docs.raku.org/routine/split for more information about split
.
See
docs.raku.org/routine/polymod for more information about polymod
.
Running it:
$ ./whats-playing 1606134123 1614591276 filelist.csv
"Les Miserables Episode 1: The Bishop (broadcast date: 1937-07-23)"
00:10:24
Spot on.
Verbose mode is really verbose here, with 4972 lines of output:
$ ./whats-playing -v 1606134123 1614591276 filelist.csv | wc
4972 57520 458027
And that's it.