Perl Weekly Challenge: Week 356

Challenge 1:

Kolakoski Sequence

You are given an integer, $int > 3.

Write a script to generate the Kolakoski Sequence of given length $int and return the count of 1s in the generated sequence. Please follow the wikipedia page for more information.

Example 1
Input: $int = 4
Output: 2

(1)(22)(11)(2) => 1221
Example 2
Input: $int = 5
Output: 3

(1)(22)(11)(2)(1) => 12211
Example 3
Input: $int = 6
Output: 3

(1)(22)(11)(2)(1)(22) => 122112
Example 4
Input: $int = 7
Output: 4

(1)(22)(11)(2)(1)(22)(1) => 1221121
Example 5
Input: $int = 8
Output: 4

(1)(22)(11)(2)(1)(22)(1)(22) => 12211212

On reading the spec, you might be tempted to think you need to write a script to generate the Kolokoski sequence. I mean "Write a script to generate the Kolakoski Sequence..." is kind of hard to misinterpret isn't it? As we shall see later, you don't. But for now let's say we do.

After perusing the Wikipedia we learn that the digit employed in the elements of the sequence alternates between 1 and 2. So we define a variable to keep track of the current digit. Initially it will be 1.

my $digit = 1;

We also need to store the sequence itself as we generate it.

my @kolakoski;

For each element of the sequence up to $int...

for 1 .. $int -> $i {

...if the current index modulo 4 is 0 or 1 we add an element consisting of 1 of whatever the current $digit is.

    if ($i %  4) < 2 {
        @kolakoski.push([$digit]);

Otherwise if the modulus is 2 or 3m we add an element consisting of 2 of whatever the current $digit is.

    } else {
        @kolakoski.push([$digit, $digit]);
    }

If the current value of $digit is 1, we change it to 2 or if it is 2, we change it to 1.

    $digit = ($digit == 1) ?? 2 !! 1;
}

After the sequence is generated, we just have to go through it and count the elements that have only 1s in them and print them.

@kolakoski.grep({ @$_.all == 1}).elems.say;

You don't hsve to actually do any of this. Sure the spec says "Write a script to generate the Kolakoski Sequence", but all it actually says we need to output is, "...the count of 1s in the generated sequence." I noticed that every even indexed element in the sequence is composed of 1's. So all we need to do to get the result is divide $int (the first command-line argument in my code) by 2, rounding up if it was an odd number.

(@*ARGS[0] / 2).round.say

(Full code on Github.)

It occurred to me that may be what was meant was the number of elements with length 1. The one liner still gives the right output with every value of $int I tried.

For the Perl version, we can't chain methods like in Raku so there will be some repetition. To save a few characters, $ARGV[0] is assigned to $_. Also we don't have .round() so instead we can just add 0.5 when the input is an odd number. Even with these extras, we are well within one line.

$_=$ARGV[0]; say $_ / 2 + ($_ % 2 ? 0.5 : 0)

(Full code on Github.)

Challenge 2:

Who Wins

It’s NFL playoff time. Since the 2020 season, seven teams from each of the league’s two conferences (AFC and NFC) qualify for the playoffs based on regular season winning percentage, with a tie-breaking procedure if required. The top team in each conference receives a first-round bye, automatically advancing to the second round.

The following games are played. Some times the games are played in a different order. To make things easier, assume the order is always as below.

- Week 1: Wild card playoffs
  - Team 1 gets a bye
  - Game 1: Team 2 hosts Team 7
  - Game 2: Team 3 hosts Team 6
  - Game 3: Team 4 hosts Team 5

- Week 2: Divisional playoffs
  - Game 4: Team 1 hosts the third seeded winner from the previous week.
  - Game 5: The highest seeded winner from the previous week hosts the second seeded winner.

- Week 3: Conference final
  - Game 6: The highest seeded winner from the previous week hosts the other winner

You are given a six character string containing only H (home) and A away which has the winner of each game. Which two teams competed in the the conference final and who won?

Example 1

NFC Conference 2024/5. Teams were Detroit, Philadelphia, Tampa Bay, Los Angeles Rams, Minnesota, Washington and Green Bay. Philadelphia - seeded second - won.

Input: $results = "HAHAHH"
Output: "Team 2 defeated Team 6"

In Week 1, Team 2 (home) won against Team 7, Team 6 (away) defeated Team 3 and Team 4 (home) were victorious over Team 5. This means the second week match ups are Team 1 at home to Team 6, and Team 2 hosted Team 4.

In week 2, Team 6 (away) won against Team 1, while Team 2 (home) beat Team 4. The final week was Team 2 hosting Team 6

In the final week, Team 2 (home) won against Team 6.
Example 2

AFC Conference 2024/5. Teams were Kansas City, Buffalo, Baltimore, Houston, Los Angeles Charges, Pittsburgh and Denver. Kansas City - seeded first - won.

Input: $results = "HHHHHH"
Output: "Team 1 defeated Team 2"
Example 3

AFC Conference 2021/2. Teams were Tennessee, Kansas City, Buffalo, Cincinnati, Las Vegas, New England and Pittsburgh. Cincinnati - seeded fourth - won.

Input: $results = "HHHAHA"
Output: "Team 4 defeated Team 2"
Example 4

NFC Conference 2021/2. Teams were Green Bay, Tampa Bay, Dallas, Los Angeles Rams, Arizona, San Francisco and Philadelphia. The Rams - seeded fourth - won.

Input: $results = "HAHAAH"
Output: "Team 4 defeated Team 6"
Example 5

NFC Conference 2020/1. Teams were Green Bay, New Orleans, Seattle, Washington, Tampa Bay, Los Angeles Rams and Chicago. Tampa Bay - seeded fifth - won.

Input: $results = "HAAHAA"
Output: "Team 5 defeated Team 1"

This seems to be a very daunting problem based on the length of the spec but it's actually quite simple once you understand it.

Let's look at the MAIN() function first.

sub MAIN(

We make sure from the beginning that the input is exactly 6 A or H characters. This way we won't have to keep checking for validity in the body of the script.

    $result where { $result.match(/^ <[AH]> ** 6 $/) } #= a string of 6 A or H characters
) {

We take the input and split it into individual characters with .comb().

    my @results = $result.comb;

To find which teams are moving on to week 2, we call the wildcardPlayoffs() function with the first three characters from the input.

    my @week2 = wildcardPlayoffs(@results[0..2]);

To find which teams are moving on to week 3, we call the divisionPlayoffs() function with the teams from the second week and the fourth and fifth characters from the input.

    my @week3 = divisionalPlayoffs(@week2, @results[3 .. 4]);

Finally, to determine the conference winner and runner-uo (a kind way of saying loser,) we call the conferenceFiinals() functions with the teams from week 3 and the sixth character of the input. It returns the winner and the loser...

    my ($winner, $loser) = conferenceFinals(@week3, @results[5]);

...which are printed out in this line.

    say "Team $winner defeated Team $loser"
}

Now let us look at each function in detail.

sub wildcardPlayoffs(@results) {

For the first week we know exactly which teams are playing each other and which are home and which are away therefore which teams will win in each matchup. Originally I had two arrays for home team winners and away team winners but they can be combined into one; even number elements will represent home and odd number elements, away.

    my @winners = (2, 7, 3, 6, 4, 5);

We define an array to hold the teams passing on tow week 2. The spec tells us team 1 automatically gets a bye so it is added here.

    my @week2 = (1);

Now for each of the three characters from $result that represent the first week, we use the letter H or A and its position to determine which element of @winners to add to @week2. We actually only have to check for H because we know from the initial validation that if it isn't H it must be A.

    for 0 ..2 -> $i {
        @week2.push(@winners[2 * $i + ((@results[$i] eq 'H') ?? 0 !! 1)]);
    }

Finally we return @week2 sorted in ascending numeric order. Sorting is key to getting the right answer for some of the examples.

    return @week2.sort;
}

divisionPlayoffs() works the same way except we push elements from @week2 to @week3.

sub divisionalPlayoffs(@week2, @results) {
    my @winners = (0, 3, 1, 2);
    my @week3;

    for 0 .. 1 -> $i {
        @week3.push(@week2[@winners[2 * $i + ((@results[$i] eq 'H') ?? 0 !! 1)]]);
    }

    return @week3.sort;
}

conferenceFinals() is simpler because by this point we only have two teams and one result.

sub conferenceFinals(@week3, $result) {
    return $result eq 'H' ?? (@week3[0], @week3[1]) !! (@week3[1], @week3[0]);
}

(Full code on Github.)

This is the Perl version. It is not terribly different from the Raku version except arrays must be passed as references which is annoying.

sub wildcardPlayoffs($results) {
    my @winners = (2, 7, 3, 6, 4, 5);
    my @week2 = (1);

    for my $i (0 ..2) {
        push @week2, $winners[2 * $i + (($results->[$i] eq 'H') ? 0 : 1)];
    }

    return sort @week2;
}

sub divisionalPlayoffs($week2, $results) {
    my @winners = (0, 3, 1, 2);
    my @week3;

    for my $i (0 .. 1) {
        push @week3, $week2->[$winners[2 * $i + (($results->[$i] eq 'H') ? 0 : 1)]];
    }

    return sort @week3;
}

sub conferenceFinals($week3, $result) {
    return $result eq 'H' ? ($week3->[0], $week3->[1]) : ($week3->[1], $week3->[0]);
}

my ($result) = @ARGV;

unless ($result && $result =~ /^[AH]{6}$/) {
    die "Need a string of 6 A or H characters\n";
}

my @results = split //, $result;

my @week2 = wildcardPlayoffs([@results[0..2]]);

my @week3 = divisionalPlayoffs([@week2], [@results[3 .. 4]]);

my ($winner, $loser) = conferenceFinals(\@week3, $results[5]);

say "Team $winner defeated Team $loser"

(Full code on Github.)