Perl Weekly Challenge: Week 326

Challenge 1:

Day of the Year

You are given a date in the format YYYY-MM-DD.

Write a script to find day number of the year that the given date represent.

Example 1
Input: $date = '2025-02-02'
Output: 33

The 2nd Feb, 2025 is the 33rd day of the year.
Example 2
Input: $date = '2025-04-10'
Output: 100
Example 3
Input: $date = '2025-09-07'
Output: 250

I am fascinated by calendars and date problems and Raku has a standard Date class which makes dealing with dates really easy. In fact the class has a .day-of-year() that solves this exact problem. So we can do a one-liner. We create a new Date object with the first command-line argument as input. (I'm not doing any validation but you really ought to in any real-world application as date formats are notoriously varied and arbitrary.) Then we call the .day-of-year() method on that object and print the result with .say(). And that's it!

Date(@*ARGS[0]).day-of-year.say

(Full code on Github.)

The Perl version is only a little more involved. We have to use a module for Date related calculations as the functions built in to Perl are rather basic. DateTime is the most versatile and complete but Time::Piece has what we need and also has the advantage of being bundled with distributions of Perl. To use it in a one-liner we have to call Perl with the -M switch like this:

-MTime::Piece

To create a Time::Piece object which is not the current date and time, we have to use the strptime() constructor. It takes two arguments, a string containing a date and a string containing the format the date is in. In my opinion this format (the international standard ISO8601 format) should be the default or autodetected but we have to spell it out. Once we have the date parsed, we caan use the yday() method to get the day of year. For some odd reason, Time::Piece starts the day of year from 0 so we have to add 1 to get the proper answer.

say Time::Piece->strptime(shift, "%Y-%m-%d")->yday + 1

(Full code on Github.)

Challenge 2:

Decompressed List

You are given an array of positive integers having even elements.

Write a script to to return the decompress list. To decompress, pick adjacent pair (i, j) and replace it with j, i times.

Example 1
Input: @ints = (1, 3, 2, 4)
Output: (3, 4, 4)

Pair 1: (1, 3) => 3 one time  => (3)
Pair 2: (2, 4) => 4 two times => (4, 4)
Example 2
Input: @ints = (1, 1, 2, 2)
Output: (1, 2, 2)

Pair 1: (1, 1) => 1 one time  => (1)
Pair 2: (2, 2) => 2 two times => (2, 2)
Example 3
Input: @ints = (3, 1, 3, 2)
Output: (1, 1, 1, 2, 2, 2)

Pair 1: (3, 1) => 1 three times => (1, 1, 1)
Pair 2: (3, 2) => 2 three times => (2, 2, 2)

First we define storage to hold the decompressed list.

my @decompressed;

Now as long as we have elements in @ints we take off the first two with .splice() and assign them to the variables $i and $j.

while @ints.elems {
    my ($i, $j) = @ints.splice(0, 2);

$j is duplicated $i number of times with the x operator. This gives a string so we need to break it up into a list of single digits with .comb(). If we .push() it to @decompressed it will be stored as a list reference so we have to "flatten" it first so elements get added individually. The | operator is one way to do this.

    @decompressed.push(| ($j x $i).comb);
}

Once all the input has been processed, we can print @decompressed. The rest of the code on this line is just so the output will be in the same format as in the spec.

say q{(}, @decompressed.join(q{, }), q{)};

(Full code on Github.)

The Perl version is a little more streamlined.

my @decompressed;

We can combine into one line what took two in Raku.

while (my ($i, $j) = splice @ints, 0, 2) {

And there is no need to flatten an appended list.

    push @decompressed, split //, $j x $i;
}

say q{(}, (join q{, }, @decompressed), q{)};

(Full code on Github.)

Update Yitzchak "ysth" Scott-Thoennes pointed out a mistake I made in my code. Perl and Raku are flexible enough that what I submitted works but I could have made things simpler.

To quote the perlop man page:

Binary x is the repetition operator. In scalar context, or if the left operand is neither enclosed in parentheses nor a qw// list, it performs a string repetition. In that case it supplies scalar context to the left operand, and returns a string consisting of the left operand string repeated the number of times specified by the right operand. If the x is in list context, and the left operand is either enclosed in parentheses or a qw// list, it performs a list repetition. In that case it supplies list context to the left operand, and returns a list consisting of the left operand list repeated the number of times specified by the right operand.

The upshot is if I had used parentheses around $j I wouldn't have needed the split(). The updated line looks like this:

push @decompressed, ($j) x $i;

Raku avoids dealing with context by reserving x for string repetion and introducing xx for list repetition. Which I knew but forgot to use. The updated Raku line is this:

 @decompressed.push(| ($j xx $i));