Perl Weekly Challenge: Building Acronym Arrays

This Perl Weekly Challenge didn’t lend itself to a song. Drat.

My learning Elixir is going to have to wait a few weeks; I’ve added some theatrical tech work to my evenings, so until I’m done with that, I’m just writing these solutions in the languages I know well.

Task 1: Acronym

You are given an array of strings and a check string.

Write a script to find out if the check string is the acronym of the words in the given array.

Example 1

Input: @str = ("Perl", "Python", "Pascal")
       $chk = "ppp"
Output: true

Example 2

Input: @str = ("Perl", "Raku")
       $chk = "rp"
Output: false

Example 3

Input: @str = ("Oracle", "Awk", "C")
       $chk = "oac"
Output: true

Approach

Pretty straightforward: go through each of the items in @str, grab the first character, append it to a string, then lowercase the result and compare it to the value of $chk. Easy-peasy.

Raku

Once again, I wanted to use Raku’s Reduction Metaoperator[ ], because it’s just so neat… but at first I couldn’t think of a way to get it to append the first character from each element without it just becoming the first character of the first and last elements. So I did this:

sub makeAcronym(@str) {
  my $acronym;
  for @str -> $s {
    $acronym ~= substr($s, 0, 1);
  }
  return $acronym.lc;
}

But then I was reading more that the reduction metaoperator gives the same result as the reduce routine, so I went and read up on that. And I saw the following:

When the list contains no elements, an exception is thrown, unless &with is an operator with a known identity value (e.g., the identity value of infix:<+> is 0). For this reason, you’re advised to prefix the input list with an initial value (or explicit identity value):

my \strings = "One good string!", "And one another good string!";
say reduce { $^a ~ $^b }, '', |strings;               # like strings.join 
 
my \numbers = 1, 2, 3, 4, 5;
say reduce { $^a > $^b ?? $^a !! $^b }, 0, |numbers; # like numbers.max 
 
sub count-and-sum-evens( (Int \count, Int \sum), Int \x ) {
    x %% 2 ?? (count+1, sum+x) !! (count, sum)
}
 
say reduce &count-and-sum-evens, (0, 0), |numbers;    # OUTPUT: «(2 6)␤»

Well, that made me think: if I reduced { $^a ~ substr($^b, 0, 1) } and made an empty string the first element of the list I was reducing, then the first time through the reduction my $^a string would be an empty string, and each subsequent time it would be the entirety of the acronym I was building. This got me to a new version:

sub makeAcronym(@str) {
  my $acronym = reduce { $^a ~ substr($^b, 0, 1) },
      ('', |@str);
  return $acronym.lc;
}

The |@str part took me a little while to grok: because ('', @str) winds up being represented internally as ['', [ 'one', 'two', 'three' ] ], I needed some way to flatten @str when it was being added to the list. I thought ('', @str.flat) would do what I wanted, but it didn’t. It turns out that if I was going to use .flat I needed to use ('', @str).flat. But then I realized it was staring me in the face in the documentation: |. This is a quick way to do a Slip:

Sometimes you want to insert the elements of a list into another list. This can be done with a special type of list called a Slip.

Another way to make a Slip is with the | prefix operator. Note that this has a tighter precedence than the comma, so it only affects a single value, but unlike the above options, it will break Scalars.

say (1, |(2, 3), 4) eqv (1, 2, 3, 4);        # OUTPUT: «True␤» 
say (1, |$(2, 3), 4) eqv (1, 2, 3, 4);       # OUTPUT: «True␤» 
say (1, slip($(2, 3)), 4) eqv (1, 2, 3, 4);  # OUTPUT: «True␤»

I was being thrown by the sample code in the reduce routine documentation because it didn’t use a @ sigil for its arrays.

Since I’d gotten it to work with reduce, could I get it to work with a reduction metaoperator? Sure. But because it wasn’t a single operator, I would need to wrap a call to a function instead:

sub firstOfSecond { $^a ~ substr($^b, 0, 1) };

sub makeAcronym(@str) {
  my $acronym = [[&firstOfSecond]] ('', |@str);
  return $acronym.lc;
}

I like that solution a lot. View the entire Raku script for this task on GitHub.

Perl

It turns out my Perl-fu is still greater than my Raku-fu, because turning the Raku code into Perl code just fell from my fingers:

use List::Util qw( reduce );

sub makeAcronym {
  my $str = shift;
  my $acronym = reduce { $a . substr($b, 0, 1) } '', @$str;
  return lc($acronym);
}

Mostly, it’s Perl’s proclivity to generate lists from scalars or arrays separated by commas. I didn’t have to do anything fancy to make sure that '', @$str was flattened to a list of scalars.
View the entire Perl script on GitHub.

Python

Because last week I was using functools.reduce, it was fresh on my mind.

from functools import reduce

def makeAcronym(str_list):
    # add empty string to beginning of list
    str_list = [''] + str_list
    acronym = reduce(lambda a, b: a + b[0], str_list)
    return acronym.lower()

View the entire Python script for this task on GitHub.


Task 2: Build Array

You are given an array of integers.

Write a script to create an array such that new[i] = old[old[i]] where 0 <= i < new.length.

Example 1

Input: @int = (0, 2, 1, 5, 3, 4)
Output: (0, 1, 2, 4, 5, 3)

Example 2

Input: @int = (5, 0, 1, 2, 3, 4)
Output: (4, 5, 0, 1, 2, 3)

Approach

This one feels weird. I think its the old[old[i]] construction that makes it a little confusing. I’m hoping that given the array old I’ll just be able to use old[old[i]] as I loop through the indices of the array and it will just produce the desired output.

I also don’t know why it specifies new.length instead of old.length, because if we’re building the array new, we don’t really know the length of the array before we start, and if we tried to have i >= old.length we’d get an out-of-bounds error (whether it was caught or not) when we tried to access old[i]. Oh, well.

Raku

sub buildArray(@old) {
  my @new;
  for 0 .. @old.elems - 1 -> $i {
    @new.push(@old[@old[$i]]);
  }
  return @new;
}

And it does produce the desired output. View the entire Raku script for this task on GitHub.

Perl

sub buildArray(@old) {
  my @new;
  foreach my $i (0 .. $#old) {
    push @new, $old[$old[$i]];
  }
  return @new;
}

One thing I like about Perl over Raku is being able to say $#old to get the index of the last element of an array instead of @old.elems - 1. View the entire Perl script on GitHub.

Python

def buildArray(old):
    new = []
    for i in range(0, len(old)):
        new.append(old[old[i]])
    return new

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-240/packy-anderson