PWC082 - Interleave String (now for real, hopefully!)

TL;DR

On we go with TASK #2 of Perl Weekly Challenge #082. Again.

In PWC082 - Interleave String (but not really!) I attempted a solution to the challenge… but it turned out to be a different one.. Let’s try again…

The challenge

Nothing really new here:

You are given 3 strings; $A, $B and $C. Write a script to check if $C is created by interleave $A and $B. Print 1 if check is success otherwise 0.

BUT the examples - when read with due care and without pre-conceptions - are explicity in showing that you can get as many characters out of either $A and $B at any given step, not only one. Which was, unfortunately, my assumption in the previous solution. Oooops šŸ˜…

The questions

At this point, I interpet the interleave as any sequence of pieces taken from either string, up to their complete consumption. Again, I’ll assume that it’s at the character level, whatever substr deems OK is fine by me.

The solution

This time the problem is more general and we can reason like this: the first character of $C can come from either $A or $B. That’s it, problem solved, see ya!

Oh… was that a tad cryptic?!? 🧐

Let’s assume the three strings are non-empty, they will have the form of a first character, followed by the rest of the string:

$A <=> $first_A . $rest_A
$B <=> $first_B . $rest_B
$C <=> $first_C . $rest_C

As we said, $first_C must be equal to either $first_A or $first_B for the interleave to apply. Let’s say it’s equal to $first_A: if we cancel out both of them, we are left with:

$A1 <=>            $rest_A
$B1 <=> $first_B . $rest_B
$C1 <=>            $rest_C

and $C1 must be the interleave of $A1 and $B1, or the interleave will not apply to the initial triple as well. The same reasoning can be done if $first_C is actually coming from $B (i.e. $first_B) so we will not repeat it.

This can be easily addressed recursively, because it’s sufficient to re-apply the process to $A1, $B1, and $C1 at this point. When $first_C can come from both $A and $B, it will be sufficient to try both paths: if one fails, we check the other one.

A recursive function, though, must have a stop condition. In our case, when any of the strings are empty, there’s no more ā€œinterleavingā€ to do and we check the remainders against each other: if they match it’s a win, otherwise… not.

So we’re ready to code the core of the solution:

sub iir ($A, $B, $C) {
   return 1 if (length($A) == 0 && $B eq $C)  # only B remained
      || (length($B) == 0 && $A eq $C)        # only A remained
      || (length($C) == 0);                   # never reached, paranoia
   my $cc = substr $C, 0, 1, ''; # chop off first char from $C...
   return (($cc eq substr $A, 0, 1) && iir(substr($A, 1), $B, $C))
       || (($cc eq substr $B, 0, 1) && iir(substr($B, 1), $A, $C));
}

The initial check is exactly the last one we discussed about the length of any string having dropped to zero. The last check (on $C) is to account for $C having dropped to zero length while there are still characters in either of the other two inputs, but it’s a paranoia because we wrap the above function in this:

sub is_interleaving ($A, $B, $C) {
   return (length($A) + length($B) == length($C)) && iir($A, $B, $C);
}

This check is a leftover from our previous version, and basically does a quick check on the lengths before running the more costly recursive function. For this reason, the check on the length of $C inside function iir becomes unnecessary (but still can remain because it will not get in the way).

Here’s the (new) complete script if you want to play with it:

#!/usr/bin/env perl
use 5.024;
use warnings;
use English qw< -no_match_vars >;
use experimental qw< postderef signatures >;
no warnings qw< experimental::postderef experimental::signatures >;

# well... let's recurse!
sub iir ($A, $B, $C) {
   return 1 if (length($A) == 0 && $B eq $C)  # only B remained
      || (length($B) == 0 && $A eq $C)        # only A remained
      || (length($C) == 0);                   # never reached, paranoia
   my $cc = substr $C, 0, 1, ''; # chop off first char from $C...
   return (($cc eq substr $A, 0, 1) && iir(substr($A, 1), $B, $C))
       || (($cc eq substr $B, 0, 1) && iir(substr($B, 1), $A, $C));
}

sub is_interleaving ($A, $B, $C) {
   return (length($A) + length($B) == length($C)) && iir($A, $B, $C);
}

my $A = shift || 'XY';
my $B = shift || 'Z';
my $C = shift || 'ZXY';
say is_interleaving($A, $B, $C);

Before closing, I’d like to thank Myoungjin Jeon again for spotting the errors in my previous interpretation of this task and until next time… have a good day!


Comments? Octodon, , GitHub, Reddit, or drop me a line!