PWC 231 A Lot to Unpack Here
Tags: perl, perlweeklychallenge, pwc
Published: 2023-08-23 17:21:21

## Perl Weekly Challenge 231
### [Task 1: Min Max](https://theweeklychallenge.org/blog/perl-weekly-challenge-231/#TASK1)
```text
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.
```
### 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:

```perl
my ($min, $max) = ($list[0], $list[0]);
for my $elem ( @list )
{
    $min = $elem if $elem < $min;
    $max = $elem if $elem > $max;
}
```

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`:
```perl
return [ grep { $_ > $min && $_ < $max } @list ];
```
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](https://theweeklychallenge.org/blog/perl-weekly-challenge-231/#TASK2)
```text
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).
```
### 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:
```perl
scalar grep { $_ >= 60 } map { substr($_, -4, 2) } @passenger;
```
We don't really need the `map`, though.  We can simplify it to one pass by doing the extraction within the `grep`:
```perl
scalar grep { substr($_, -4, 2) >= 60 } @passenger
```
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 **A**_n_, 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:
```perl
scalar grep { (unpack("A10 A1 A2 A2", $_))[2] >= 60 } @passenger;
}
```
Probably the `substr` version is more accessible to the average programmer, but here, as in Lake Wobegon, we strive that everyone be above average.