DEV Community

Bob Lied
Bob Lied

Posted on

PWC 231 A Lot to Unpack Here

Perl Weekly Challenge 231

Task 1: Min Max

You are given an array of distinct integers.

Write a script to find all elements that is neither
minimum nor maximum. Return -1 if you can’t.
Enter fullscreen mode Exit fullscreen mode

Example 1

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

Example 2

Input: @ints = (3, 1)
Output: -1

Example 3

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

Deep Thoughts

The problem amounts to finding all the elements that are between the minimum and maximum. So, we must determine the minimum and maximum. I can think of several ways to do that:

  • Make a pass over the list to find the minimum, and another pass to find the maximum.
  • Use List::Util::max and List::Util::min
  • In one pass, check for minimum and maximum at the same time.
  • Use List::MoreUtils::minmax to get both
  • Sort the list numerically and take the first and last element.

There is an intriguing note in the documentation of List::MoreUtils::minmax that this can be done with only 3N/2-2 comparisons. I'm not clever enough to think of any way that doesn't involve checking all N elements twice.

For the joy of programming, let's do our own scan of the list to find minimum and maximum simultaneously:

my ($min, $max) = ($list[0], $list[0]);
for my $elem ( @list )
{
    $min = $elem if $elem < $min;
    $max = $elem if $elem > $max;
}
Enter fullscreen mode Exit fullscreen mode

With $min and $max in hand, we need to make a second pass to select the values, but instead of an explicit loop, we select implicitly using grep:

return [ grep { $_ > $min && $_ < $max } @list ];
Enter fullscreen mode Exit fullscreen mode

To ensure that we get a list to return, we create an array context by putting the grep inside a square-bracket pair. Since the specification requires that we return -1 for an empty list, this needs to be wrapped in some code that checks for [] and returns -1 instead. Also, the return value is an array reference instead of an array, so the wrapping code will have to de-reference to show the actual values.

Task 2: Senior Citizen

You are given a list of passenger details in 
the form “9999999999A1122”, where 9 denotes
the phone number, A the sex, 1 the age and 
2 the seat number.

Write a script to return the count of all
senior citizens (age >= 60).
Enter fullscreen mode Exit fullscreen mode

Example 1

Input: @list = ("7868190130M7522","5303914400F9211","9273338290F4010")
Ouput: 2

The age of the passengers in the given list are 75, 92 and 40.
So we have only 2 senior citizens.

Example 2

Input: @list = ("1313579440F2036","2921522980M5644")
Ouput: 0

The ages are 20 and 56, so none greater than 60.

Deep Thoughts

"Senior Citizen", huh? I feel attacked. I guess nobody over 100 can fly on this airline; probably a fair assumption.

Let's raise a glass to data in fixed-length formats. Expensive memory and the legacy of 80-column punch cards gave us many ingenious ways to encode data into a few bytes, the more incomprehensible, the better. In one project, we had a suite of tools to examine how data was packed into bit fields in C structures and I once caused an outage by trying to assign 8 bits into a 7-bit field. Ah, good times.

It also gave us the bright idea that it only takes two digits to designate a year, which gave us nearly a decade of full employment right up to December 31, 1999. But it's 2023, which means this year's graduating computer science class was born in this century and thinks of Y2K as ancient mythical lore, so two digits for years is fashionable again. I can already see where this is going, but ... Hey, you kids get off my lawn!

Anyway, this seems easy enough: extract the two-character sub-string that is the age. The substr function is right there, built into Perl, and if we can count correctly, we have it. The age is encoded at position 11, I think. substr allows negative offsets, so it might be easier to count backwards from the end of the string -- I can confidently count to -4, whereas 11 runs out of fingers.

Broken down in a few steps, we use map to reduce each passenger to its age, and then grep to select those of gray hair. grep is flexible depending on context, and since we want the count, not the list of ages, we coerce scalar context:

scalar grep { $_ >= 60 } map { substr($_, -4, 2) } @passenger;
Enter fullscreen mode Exit fullscreen mode

We don't really need the map, though. We can simplify it to one pass by doing the extraction within the grep:

scalar grep { substr($_, -4, 2) >= 60 } @passenger
Enter fullscreen mode Exit fullscreen mode

Perl also gives another option for fixed-length data: the pack and unpack functions. The subject actually gets quite deep; it has its own soporific tutorial (perldoc perlpacktut).

Text in fixed columns is one of the simpler applications of unpack. The template specification for text is An, where n is the width of the field. In this case, we have four fields: 10 digits for phone, one character for sex (let's not touch that hot wire today), two for age, and two for seat number. The template for that is "A10 A1 A2 A2", which is a little more obvious than the magical 11 or -4 that we need for substr. Using unpack will yield a four-element list that we can index into:

scalar grep { (unpack("A10 A1 A2 A2", $_))[2] >= 60 } @passenger;
}
Enter fullscreen mode Exit fullscreen mode

Probably the substr version is more accessible to the average programmer, but here, as in Lake Wobegon, we strive that everyone be above average.

Top comments (0)