The Weekly Challenge ‐ Perl and Raku

CY's Take on The Weekly Challenge #138 Task 1 and Some Previous Tasks on Calendar Date

Comparing Perl, Java and Julia

If you want to challenge yourself on programming, especially on Perl and/or Raku, go to https://theweeklychallenge.org, code the latest challenges, submit codes on-time (by GitHub or email).

Do tell me, if I am wrong or you strongly oppose my statements!

It's time for challenges in Week #138, specific in Task 1 !

But you gonna also see details to Week #132 Task 1 and Week #137 Task 1 on this blogpost.



(fair use, from Ole Arntzen)
Download Your 12 sided calendar
Week #132 Task 1: Mirror Dates

Input: a date (yyyy/mm/dd). Output: "backward mirror date" and "forward mirror date".

[For the task.] Assuming today is 2021/09/22.
Example 1:

Input: 2021/09/18
Output: 2021/09/14, 2021/09/26

On the date you were born, someone who was your current age, would have been born on 2021/09/14.
Someone born today will be your current age on 2021/09/26.
Week #137 Task 1: Long Year

Input: N/A. Output: number of long years between 1900 and 2100.

A year is Long if it has 53 weeks. (For more information...) wikipedia
Week #138 Task 1: Work Days

Input: a positive integer indicated the year. Output: number of work days in that year.

For the task, we consider, Monday - Friday as workdays.

Note that I decide not to use any modules outside the standard library of each programming language. I just checked out the handy Date::Calc on CPAN, and it equips programmer many handy functions (like what are in the standard libary of Java or Julia), better than my struggling usage of Time::Local.

In terms of data structures or algorithms, these three tasks are quite simple. On the practical side, we have to be familiar with the date(/time) library we are using. As an early hint for the summary, "the standard library" of Perl solutions are the most complicated.

Overview for the Languages

I choose to code both in Java, Julia and Perl for the tasks.

Java

Though not being a purely object-oriented programming language, one must know the rules of Java's typing and Java's Object-Oriented system in order to use it effectively. For the tasks mentioned, java.time.LocalDate has been my friend. (Besides java.time.LocalDate, there are java.time.LocalDateTime, java.time.Year, java.time.YearMonth and java.time.ZonedDateTime to handle the date and/or time operation.)

Julia

The Dates module is within standard library of Julia and it handles both date and time operation. I made the largest advantage of the library through using Dates.week() in Week 137. The resulting code has just 15 lines essentially.

Julia is a very young dynamic language. From the info from its official website, Julia 1.0 was released in August 2018, and I am using version 1.6.3.

The developers stated that they are "greedy" and want a multi-purpose language. Many fans of Julia seems agree to the ambition. Besides scientific computing and (pure/applied) math modules, for evidence of the ambition, they have also module for documentation(Documenter.jl).

By the way, the developers also said they want the language "as natural for string processing as Perl" and "as good at gluing programs together as the shell". I haven't tried Julia's capacity on these areas.

I also have to admit here that I haven't well handle many features of Julia, e.g. macro (idea from LISP) and its rich type system. In order to keep being (relatively) rich, one has to be careful and make some inconvenience in life. To see an example:

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.6.3 (2021-09-23)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> B = rand(3, 3)  # a random matrix, so few code to generate
3×3 Matrix{Float64}:
 0.780488  0.758598   0.0491861
 0.612032  0.0468023  0.0547018
 0.131136  0.334803   0.170626

julia> variable = B[1,2] # get its entry on row 1 and col 2
0.758597987877625

julia> # note that Julia's arrays begin with 1 by default 

julia> # Now I want an 3 x 3 identity matrix...

julia> I = Matrix{Int}(I, 3, 3)
ERROR: UndefVarError: I not defined
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

julia> I = ([1, 0, 0], [0, 1, 0], [0, 0, 1])
([1, 0, 0], [0, 1, 0], [0, 0, 1])

julia> B + I
ERROR: MethodError: no method matching +(::Matrix{Float64}, ::Tuple{Vector{Int64}, Vector{Int64}, Vector{Int64}})
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:560
  +(::Array, ::Array...) at arraymath.jl:43
  +(::Array, ::SparseArrays.AbstractSparseMatrixCSC) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.6/SparseArrays/src/sparsematrix.jl:1745
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[7]:1

julia> I = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
3-element Vector{Vector{Int64}}:
 [1, 0, 0]
 [0, 1, 0]
 [0, 0, 1]

julia> B + I
ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(3), Base.OneTo(3)), must have singleton at dim 2")
Stacktrace:
skipped

julia> I = [1, 0, 0; 0, 1, 0; 0, 0, 1]
ERROR: syntax: unexpected semicolon in array expression around REPL[10]:1
Stacktrace:
 [1] top-level scope
   @ REPL[10]:1

julia> I = [1 0 0; 0 1 0; 0 0 1]
3×3 Matrix{Int64}:
 1  0  0
 0  1  0
 0  0  1

julia> B + I
3×3 Matrix{Float64}:
 1.78049   0.758598  0.0491861
 0.612032  1.0468    0.0547018
 0.131136  0.334803  1.17063

julia> # finally get it right after referencing some materials

julia> using LinearAlgebra
WARNING: using LinearAlgebra.I in module Main conflicts with an existing identifier.

julia> B + LinearAlgebra.I
3×3 Matrix{Float64}:
 1.78049   0.758598  0.0491861
 0.612032  1.0468    0.0547018
 0.131136  0.334803  1.17063

julia> A = rand(2, 2)
2×2 Matrix{Float64}:
 0.158673    0.498452
 0.00449048  0.731519

julia> A + LinearAlgebra.I
2×2 Matrix{Float64}:
 1.15867     0.498452
 0.00449048  1.73152

julia> # get it right by the standard package

Perl

(It is most likely Perl hackers, especially TWC participants, would read this blogpost, should I still introduce Perl for consistency? For the virtue of Laziness, and self knowledge of the limitations of my ability, I should skip it!)

Okay. It is worth mentioned that Perl is the oldest among the programming languages in this blogpost. Perl 1.000 released on 18th December, 1987.

If you have heard some bad words about Perl, I should refer you to Mark Gardner's article in October: The reports of Perl’s death have been greatly exaggerated.

From my limited exploration, I feel that the losing popularity of Perl is mostly contributed by the popularity Python, which has targeted at a similar group of users. Many people around me have learnt Python as their first programming language, so they stick to it, and think Python's quite good without actual comparsion. I just want to raise a stupid proposal: unlike the trend ‐ learning programming begins with Python or JavaScript, a beginner learns programming the best through learning LISP(Common Lisp or Scheme? doesn't matter for the starter), Smalltalk and a language they are going to work with. Yes, learn 3 within the same period of time. The current scope of LISP or Smalltalk makes them sutiable for beginners without overwhelming beginners with fancy external libraries or syntactic sugar ‐ learners can learn these stuff for the remaining "programming language to be began with". I like the consistent and pure syntax of LISP or Smalltalk brings.

Anyway, if one would like to learn the *nix systems, Perl is a great language to start with, since it has many ideas from the Unix shell script ‐ while Perl makes some improvements.

Put a full stop to my silly and limited opinion (as a record of what discussions I saw in some days before).

More on Perl: module Time::gmtime and Time::localtime

I discover the standard library modules Time::gmtime and Time::localtime during drafting of this blogpost. They ‐ actually, just one of them ‐ I decided to stick with gmtime after cleaning up ‐ greatly improves the readability of the Perl codes.

How Time::gmtime have improved the codes? Let us see two excerpts, in the tasks "Mirror Dates" and "Long Year":

Mirror Dates

Before:

return
    ($d_s->[5]+1900)."/"
    .($d_s->[4]<=8 ? 0 : "").($d_s->[4]+1)."/"
    .($d_s->[3]<10 ? 0 : "").($d_s->[3])
    .", "
    .($d_j->[5]+1900)."/"
    .($d_j->[4]<=8 ? 0 : "").($d_j->[4]+1)."/"
   .($d_j->[3]<10 ? 0 : "").($d_j->[3]);

After:

return
    ($d_s->year()+1900)."/"
    .($d_s->mon()<=8 ? 0 : "").($d_s->mon()+1)."/"
    .($d_s->mday()<10 ? 0 : "").($d_s->mday())
    .", "
    .($d_j->year()+1900)."/"
    .($d_j->mon()<=8 ? 0 : "").($d_j->mon()+1)."/"
    .($d_j->mday()<10 ? 0 : "").($d_j->mday());

Long Year

Before:

my %wday = ( 
    "Mon" => 1,  
    "Tue" => 2,  
    "Wed" => 3,  
    "Thu" => 4,  
    "Fri" => 5,  
    "Sat" => 6,  
    "Sun" => 7,  
);  

my $fourthJan = localtime timegm_nocheck 0, 0, 0, 4, 0, $y; 

my $a = $wday{(split " ", $fourthJan)[0]};

#...skip some code

if ($a >= 4 && $b >= 4) {
    say $y; 
    $c++;
}

After:

my $fourthJan = gmtime timegm_nocheck 0, 0, 0, 4, 0, $y; 

my $a = $fourthJan->wday();

#...skip some code

if (($a >= 4 || $a == 0) && ($b >= 4 || $b == 0)) {
    say $y; 
    $c++;
}   

This is the end of the introduction. We are going to see how CY's takes for the three tasks now.


TWC #132 Task: Mirror Dates

Short Description
Input: a date (yyyy/mm/dd). Output: "backward mirror date" and "forward mirror date".

The main point is found out the number of days inbetween "today" and the date who someone was born.

Java on Mirror Dates

I use the toEpochDay method to calculate the difference.

public static void mirror_dates (int birth_year,int birth_month, int birth_day) {
    LocalDate my_today = LocalDate.of(2021,9,22);
    LocalDate birthday = LocalDate.of(birth_year, birth_month, birth_day);
    long y1 = my_today.toEpochDay() - birthday.toEpochDay();
    LocalDate d_senior = birthday.minusDays(y1);
    LocalDate d_junior = my_today.plusDays(y1);
    System.out.println(d_senior);
    System.out.println(d_junior);
}   
Julia on Mirror Dates

It is quite straightforward, but one has to look up the functions of Dates. Dates.values(my_today - my_date_of_birth) leads to what we want.

function mirror_dates(my_date_of_birth)
   my_today = Date(2021,09,22)
   y1 = Dates.values(my_today - my_date_of_birth)
   d_senior = my_date_of_birth - Dates.Day(y1)
   d_junior = my_today + Dates.Day(y1)
   println(d_senior)
   println(d_junior)
end
Perl on Mirror Dates

timelocal_nocheck or timegm_nocheck gives the epoch seconds. Then we can count the difference and then divide it by 86400 (== 24*60*60).

sub mirror {
    my @arr_today = (22, 8, 2021); # Wed Sep 22 2021
    my $_today = timegm_nocheck(0, 0, 0, @arr_today);
    my @arr_birth = ($_[2], $_[1]-1, $_[0]);
    my $_birth = timegm_nocheck(0, 0, 0, @arr_birth);
    my $y1 = int (($_today - $_birth)/86400);
    my $d_senior = gmtime timegm_nocheck 0, 0, 0,
                        $arr_birth[0]-$y1, $arr_birth[1], $arr_birth[2];
    my $d_junior = gmtime timegm_nocheck 0, 0, 0,
                        $arr_today[0]+$y1, $arr_today[1], $arr_today[2];
    return [ $d_senior, $d_junior ];
}

sub mirror_str {
    my ($byear, $bmonth, $bday) = split /\//, $_[0];
    $bmonth =~ s/^0//;  # remove leading zeros
    $bday =~ s/^0//;    # remove leading zeros
    my ($d_s, $d_j) = mirror($byear, $bmonth, $bday)->@*;

    return
         ($d_s->year()+1900)."/"
        .($d_s->mon()<=8 ? 0 : "").($d_s->mon()+1)."/"
        .($d_s->mday()<10 ? 0 : "").($d_s->mday())
        .", "
        .($d_j->year()+1900)."/"
        .($d_j->mon()<=8 ? 0 : "").($d_j->mon()+1)."/"
        .($d_j->mday()<10 ? 0 : "").($d_j->mday());
}

ok mirror_str("2021/09/18") eq "2021/09/14, 2021/09/26", "Example 1";

# maybe I should further refactor the code by adding one more subroutine supporting &mirror_str

TWC #137 Task: Long Year

Short Description
Input: N/A. Output: number of long years between 1900 and 2100.

According to Wikipedia: ISO 8601,

There are several mutually equivalent and compatible descriptions of week 01:

Since 365/7 = 52...1 and 366/7 = 52...2, for a Long Year (== a year having 53 weeks), it should have the 01st Jan included in its Week 01 and 31st Dec included in its Week 53.

From Wikipedia, we get four appoaches to tackle the task! CY's way to tackle it is checking on 04th Jan and 31st Dec: if 04th Jan, YYYY is on or after Thursday and 31st Dec, YYYY is on or after Thursday (taken Sunday as the 7th day of the week), then we get a long year.

Java on Long Year
    LocalDate fourthJan = LocalDate.of(y, 1, 4); 
    int a = gdow( (fourthJan.getDayOfWeek()).toString() );
    LocalDate YearLastDay = LocalDate.of(y, 12, 31);
    int b = gdow( (YearLastDay.getDayOfWeek()).toString() );
    if (a >= 4 && b >= 4)
    {   
        System.out.println(y);
        c++;
    } 



public static int gdow(String a)
{
    switch(a)
    {
        case "MONDAY": return 1;
        case "TUESDAY": return 2;
        case "WEDNESDAY": return 3;
        case "THURSDAY": return 4;
        case "FRIDAY": return 5;
        case "SATURDAY": return 6;
        case "SUNDAY": return 7;
    }
    return 0;
}
  
Julia on Long Year

There is a Dates.week() function which we can take advantage of

t(y) = Date(y, 1, 4)
e(y) = Date(y, 12, 31)

#... skip some code

    if Dates.week(e(y)) - Dates.week(t(y)) == 52
        println(y)
        global c = c+1
    end
Perl on Long Year
    my $fourthJan = gmtime timegm_nocheck 0, 0, 0, 4, 0, $y;

    my $a = $fourthJan->wday();

    my $yearLastDay = gmtime timegm_nocheck 0, 0, 0, 31, 11, $y;

    my $b = $yearLastDay->wday();
    if (($a >= 4 || $a == 0) && ($b >= 4 || $b == 0)) {
        say $y;
        $c++;
    }

TWC #138 Task: Work Days

Short Description
Input: a positive integer indicated the year. Output: number of work days in that year.

After calculation, we discover that there are only three possible values of number of work days of a year: 260, 261 and 262. One may play some tricks on this piece of info, but CY's laziness causes her have no time for this trick.

In the codes, we count the number of weeks between the first Monday on Jan and last Monday of Dec, then do a primary school arithmetic operation: (days in between) / 7 * 5;, and then add the work days in the beginning of the year and the work days in the end of the year which haven't covered.

Java on Work Days

The method getDayOfYear() in java.time.LocalDate is crucial.

public static int numOfWorkDays (int year) {
    LocalDate MondayJan = LocalDate.of(year, 1, 1);
    int ans = 0;
    int ga = gd(MondayJan);
    while (ga != 1)
    {
        if (ga <= 5 && ga >= 2)
            ans++;
        MondayJan = MondayJan.plusDays(1);
        ga = gd(MondayJan);
    }
    LocalDate lastDay = MondayJan.plusWeeks(51);
    ans += 51*5;
    int gb = gd(lastDay);
    while (lastDay.getDayOfYear() < lastDay.lengthOfYear())
    {
        if (gb <= 5 && gb >= 1)
            ans++;
        lastDay = lastDay.plusDays(1);
        gb = gd(lastDay);
    }
    ans += gb <= 5 ? 1 : 0;
    return ans;
}



public static int gd(LocalDate date)
{
    var a = date.getDayOfWeek();
    switch(a)
    {
        case MONDAY: return 1;
        case TUESDAY: return 2;
        case WEDNESDAY: return 3;
        case THURSDAY: return 4;
        case FRIDAY: return 5;
        case SATURDAY: return 6;
        case SUNDAY: return 7;
    }
    return 0;
}

Julia on Work Days

The function Dates.dayofyear() is crucial.

function num_of_work_days(year)
    ans = 0
    my_day = Date(year, 1, 1)
    while Dates.dayofweek(my_day) != Dates.Monday
        if workday(my_day)
            ans += 1
        end
        my_day += Dates.Day(1)
    end

    FirstMondayJan = my_day

    SundayDec = Dates.lastdayofweek(Date(year,12,31))
    if (Dates.year( SundayDec + Dates.Day(1) ) == year+1)
        SundayDec -= Dates.Day(7)
    end


    LastMondayDec = SundayDec + Dates.Day(1);

    ans += 5*(Dates.dayofyear(LastMondayDec) - Dates.dayofyear(FirstMondayJan))/7
    ans = convert(Int64, ans)

    my_day = LastMondayDec
    while Dates.year(my_day) == year
        if workday(my_day)
            ans += 1
        end
        my_day += Dates.Day(1)
    end

    return ans
end
Perl on Work Days

Calculate the formula with epoch seconds.

sub count_work_days {
    my $y = $_[0];

    my $_01Jan = localtime timelocal_nocheck 0, 0, 0, 1, 0, $y;
    my $d_01Jan = $_01Jan->wday();
    my $diff = (1 - $d_01Jan) % 7;

    my $firstMonday = timelocal_nocheck 0, 0, 0, 1+$diff, 0, $y;

    my $_31Dec = localtime timelocal_nocheck 0, 0, 0, 31, 11, $y;

    my $d_31Dec = $_31Dec->wday();

    my $back_diff = ($d_31Dec - 5) % 7;


    my $MondayAfterLastFriday
        = timelocal_nocheck 0, 0, 0, 3-$back_diff, 0, $y+1;

    my $ans
        = 5 * int (
            ($MondayAfterLastFriday
            -$firstMonday)
                /86400/7
          );


    $ans += 6-$d_01Jan if $d_01Jan > 1;
    $ans += $d_31Dec if $d_31Dec < 5;

    return $ans;
}

Summary

The article Don't Just Learn the Language, Understand Its Culture by Anders Norås, from the book 97 Things Every Programmer Should Know (Feb 2010, O'Reilly), has pushed me to finish this blogpost. We have seen Perl looks a bit complicated relative to Java and Julia in some of three tasks, but if CY plays with Date::Calc, every Perlish thing becomes as convenient as in Java or Julia.

Departing words: CPAN is a part of Perl culture. If you pull out Perl alone from the vast amount of CPAN modules, it is unwise!

There is Time::Piece as a core module in Perl. (Added on 15th Nov, 2021.)

Stay alert and healthy! □


About the image: I used to amusing my tutorial students by giving them the dodecahedron calenders in order to move them by the beauty of mathematics.

Except from images and codes from other personnels, the content of this blogpost is released under a copyleft spirit. One may share (full or partial) content of this blogpost on other platform if you share it under the free and open content spirit.

References

Perl:
https://metacpan.org/pod/Time::Local

Julia:
https://docs.julialang.org/en/v1/stdlib/Dates/

Java:
LocalDate (Java Platform SE 8)

links for CY's full codes:
Perl
ch-1.pl #132
ch-1.pl #137
ch-1.pl #138

Julia
ch-1.jl #132
ch-1.jl #137
ch-1.jl #138

Java
P.S. From the TWC Java expert Mohammad Meraj Zia's code, I have learnt that I have missed the java.time.DayOfWeek library. So gd() and gdow() in the above codes can be simplified.
MirrorDates.java // 132
LongYear.java // 137
WorkDays.java // 138


Contact on twitter: @e7_87.

Discuss via GitHub issues: here.

Email: fungcheokyin at gmail.com

Created Date: 14th November, 2021.