Perl Weekly Challenge: Merge the Maximum String Pairs

Finally, the challenge needs 9 bits for its number! Perl Weekly Challenge 0x100!

Sorry, no music this week. It’s just not coming to me…

Task 1: Maximum Pairs

You are given an array of distinct words, @words.

Write a script to find the maximum pairs in the given array. The words $words[i] and $words[j] can be a pair one is reverse of the other.

Example 1

Input: @words = ("ab", "de", "ed", "bc")
Output: 1

There is one pair in the given array: "de" and "ed"

Example 2

Input: @words = ("aa", "ba", "cd", "ed")
Output: 0

Example 3

Input: @words = ("uv", "qp", "st", "vu", "mn", "pq")
Output: 2

Approach

Ok, the easiest way to do this is to step through the array and compare each element to the reverse of the other elements. But once we’ve compared an element to all the others, we don’t have to compare it again, even if it wasn’t part of a pair. Furthermore, once we’ve found a pair, we don’t need to compare either of those elements to any others, since there can be only two elements in a pair.

Raku

Of course, I figured that reverse was how I would reverse a string in Raku. Nor according to the docs, though:

Note that reverse always refers to reversing elements of a list; to reverse the characters in a string, use flip.

sub maximumPairs(@words) {
  my $count = 0;
  while (@words) {
    # the the first word off the list
    my $first = @words.shift;
    # now compare to the rest of the words in the list
    for 0 .. @words.end -> $i {
      my $second = @words[$i];
      if ($first eq $second.flip) {
        # we found a pair
        $count++;
        # remove @words[$i] from the list
        @words.splice($i, 1);
        # we don't need to compare any more words to $first
        last;
      }
    }
  }
  return $count;
}

View the entire Raku script for this task on GitHub.

Perl

But in Perl, I get to use the familiar reverse.

sub maximumPairs(@words) {
  my $count = 0;
  while (@words) {
    # the the first word off the list
    my $first = shift @words;
    # now compare to the rest of the words in the list
    for my $i (0 .. $#words) {
      my $second = $words[$i];
      if ($first eq reverse $second) {
        # we found a pair
        $count++;
        # remove @words[$i] from the list
        splice(@words, $i, 1);
        # we don't need to compare any more words to $first
        last;
      }
    }
  }
  return $count;
}

View the entire Perl script for this task on GitHub.

Python

I remembered that Python’s pop not only worked on the end of a list, but could also remove an element anywhere in the list (pop[0] is the equivalent of shift in other languages, but pop[i] is effectively splice(i, 1)), but I had to look up how to reverse a string, and I came across this nice idiomatic use of Python’s extended slice syntax. The syntax is [start:stop:step]; because we don’t specify a start and a stop, we operate on the entire string, and by specifying a step of -1, we step backwards through the string. We could also have used ''.join(reversed(s)), but from what I read, that’s actually slower than the string slice.

I still find it odd that Python doesn’t have an increment (++) operator.

def maximumPairs(words):
    count = 0
    while words:
        # the the first word off the list
        first = words.pop(0)
        # now compare to the rest of the words in the list
        for i in range(len(words)):
            second = words[i]
            if first == second[::-1]:
                # we found a pair
                count += 1
                # remove words[i] from the list
                words.pop(i)
                # we don't need to compare
                # any more words to first
                break
    return count

View the entire Python script for this task on GitHub.


Task 2: Merge Strings

You are given two strings, $str1 and $str2.

Write a script to merge the given strings by adding in alternative order starting with the first string. If a string is longer than the other then append the remaining at the end.

Example 1

Input: $str1 = "abcd", $str2 = "1234"
Output: "a1b2c3d4"

Example 2

Input: $str1 = "abc", $str2 = "12345"
Output: "a1b2c345"

Approach

Really, my approach to the first task is informing my approach to this one, because in that task I was pulling elements off a list. This time, if I break the strings into arrays and shift off the characters in alternation, I’ll get the result I’m looking for.

Raku

Note I’m using arrays being evaluated in scalar context in multiple places, where they work effectively like booleans: if @array has elements in it, it evaluates true.

sub mergeStrings($str1, $str2) {
  my @chars1 = $str1.split('', :skip-empty);
  my @chars2 = $str2.split('', :skip-empty);
  my $result;
  while (@chars1 || @chars2) {
    $result ~= @chars1.shift if @chars1;
    $result ~= @chars2.shift if @chars2;
  }
  return $result;
}

View the entire Raku script for this task on GitHub.

Perl

Besides the change in split and shift syntax and the concatenation character changing from ~ to ., the Perl version is exactly the same as the Raku version.

sub mergeStrings($str1, $str2) {
  my @chars1 = split(//, $str1);
  my @chars2 = split(//, $str2);
  my $result;
  while (@chars1 || @chars2) {
    $result .= shift(@chars1) if @chars1;
    $result .= shift(@chars2) if @chars2;
  }
  return $result;
}

View the entire Perl script for this task on GitHub.

Python

The nice thing in Python is you can convert a string into a list of characters with list(s).

def mergeStrings(str1, str2):
    chars1 = list(str1)
    chars2 = list(str2)
    result = ''
    while chars1 or chars2:
        if chars1:
            result += chars1.pop(0)
        if chars2:
            result += chars2.pop(0)
    return result

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