Two-Hundred Fifty Perl Weekly Challenges! Two-Hundred Fifty problems so clear…

Yeah, I’m trying to get back to music for the theme of the post, and for some reason the number 250 made me think of a song from Rent

If PWC runs for 525,600 weeks, that will be over ten thousand years. I can dream that we’re still using Perl or Raku that long. Anyway, onward to Perl Weekly Challenge 250!


Task 1: Smallest Index

You are given an array of integers, @ints.

Write a script to find the smallest index i such that i mod 10 == $ints[i] otherwise return -1.

Example 1

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

i=0: 0 mod 10 = 0 == $ints[0].
i=1: 1 mod 10 = 1 == $ints[1].
i=2: 2 mod 10 = 2 == $ints[2].
All indices have i mod 10 == $ints[i], so we return the smallest index 0.

Example 2

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

i=0: 0 mod 10 = 0 != $ints[0].
i=1: 1 mod 10 = 1 != $ints[1].
i=2: 2 mod 10 = 2 == $ints[2].
i=3: 3 mod 10 = 3 != $ints[3].
2 is the only index which has i mod 10 == $ints[i].

Example 3

Input: @ints = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0)
Output: -1
Explanation: No index satisfies i mod 10 == $ints[i].

Approach

When I was thinking about this problem, I found myself asking “Why are we doing i mod 10? So the list can’t be more than 10 elements long?” No, I realized: so the elements are only single digits. Take a look at example 3: if we line them up so we can see what the indices are next to the values, we can see that none of the values match the indices.

ints[0] = 1
ints[1] = 2
ints[2] = 3
ints[3] = 4
ints[4] = 5
ints[5] = 6
ints[6] = 7
ints[7] = 8
ints[8] = 9
ints[9] = 0

But what if we inserted another value before the end? What if the end looked like this?

ints[8]  = 9
ints[9]  = 8
ints[10] = 0

Suddenly, we’d have i mod 10 == 0, which would be the value of the final element. This also makes the comparison slightly more interesting than “find the minimal value for i where i = ints[i]“.

Initially, I was leaning towards using a map to generate all the possible cases where i = ints[i] and then use min to get the smallest one, but a voice in my head said “You’re looping over all the values anyway… just find the first one and exit when you find it. It’s faster.”

Raku

sub smallestIndex(@ints) {
  for 0 .. @ints.end -> $i {
    return $i if ($i mod 10) == @ints[$i];
  }
  return -1;
}

View the entire Raku script for this task on GitHub.

Perl

Again, the Perl version is almost identical to the Raku version: the modulo operator is % instead of mod, the for loop is structured differently, and we have to use $ as the sigil to access individual array elements.

sub smallestIndex(@ints) {
  foreach my $i ( 0 .. $#ints ) {
    return $i if ($i % 10) == $ints[$i];
  }
  return -1;
}

View the entire Perl script for this task on GitHub.

Python

As always, I need to remind myself that a) Python’s range function goes up to but does not include the largest value, and b) if you only pass a single value, the range starts at 0. So range(4) returns the range 0, 1, 2, 3.

def smallestIndex(ints):
    for i in range(len(ints)):
        if (i % 10) == ints[i]:
            return i
    return -1

View the entire Python script for this task on GitHub.


Task 2: Alphanumeric String Value

You are given an array of alphanumeric strings.

Write a script to return the maximum value of alphanumeric string in the given array.

The value of alphanumeric string can be defined as

a) The numeric representation of the string in base 10 if it is made up of digits only.
b) otherwise the length of the string

Example 1

Input: @alphanumstr = ("perl", "2", "000", "python", "r4ku")
Output: 6

"perl" consists of letters only so the value is 4.
"2" is digits only so the value is 2.
"000" is digits only so the value is 0.
"python" consists of letters so the value is 6.
"r4ku" consists of letters and digits so the value is 4.

Example 2

Input: @alphanumstr = ("001", "1", "000", "0001")
Output: 1

Approach

This could probably be done in a single loop, but I want to make things clearer and easier to read, so I’m breaking it out into two parts: a function that, given a string, returns its value according to the criteria above, and then a map that calls that function for each string in an array to generate an array of string values, and we can use max to find the largest value. See? I got to use my map!

Raku

sub alphanumValue($str) {
  if ($str ~~ /^\d+$/) {
    return Int($str);
  }
  return $str.chars;
}

sub maxAlphanumValue(@alphanumstr) {
  my @values = @alphanumstr.map({ alphanumValue($_) });
  return @values.max;
}

View the entire Raku script for this task on GitHub.

Perl

Perl doesn’t have a max function built in, so I’m using the one everybody uses in List::Util. Also, I’m using the old trick of adding 0 to a string to convert it explicitly into a numeric value.

use List::Util qw( max );

sub alphanumValue($str) {
  if ($str =~ /^\d+$/) {
    return $str + 0;
  }
  return length($str);
}

sub maxAlphanumValue(@alphanumstr) {
  my @values = map { alphanumValue($_) } @alphanumstr;
  return max(@values);
}

View the entire Perl script for this task on GitHub.

Python

For the Python solution, I’m pre-compiling the regular expression once outside the alphanumValue function. I could have done that in Raku or Perl for a minor performance gain, but I’m doing it here because the cleanest way to do the match is to compile the regex to an re object and then call .match() on that object with the string. Since we needed to compile it separately from the match call, it made sense to put that compile outside the function so it’s only done once. In Raku and Perl, the compiling takes place automagically, so doing it separately doesn’t look as clean.

import re

is_numeric = re.compile('^\d+$')

def alphanumValue(strval):
    if (is_numeric.match(strval)):
        return int(strval)
    return len(strval)

def maxAlphanumValue(alphanumstr):
    values = [ alphanumValue(x) for x in alphanumstr ]
    return max(values)

View the entire Python script for this task on GitHub.


Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-250/packy-anderson