The Bear's Den

Enter at your own risk

Magic Parentheses

Task 1: Longest Parenthesis

Submitted by: Mohammad Sajid Anwar


You are given a string containing only ( and ).

Write a script to find the length of the longest valid parenthesis.

Example 1

Input: $str = '(()())'
Output: 6

Valid Parenthesis: '(()())'

Example 2

Input: $str = ')()())'
Output: 4

Valid Parenthesis: '()()' at positions 1-4.

Example 3

Input: $str = '((()))()(((()'
Output: 8

Valid Parenthesis: '((()))()' at positions 0-7.

Example 4

Input: $str = '))))((()('
Output: 2

Valid Parenthesis: '()' at positions 6-7.

Example 5

Input: $str = '()(()'
Output: 2

Valid Parenthesis: '()' at positions 0-1 and 3-4.

Solution

The rules for valid parentheses may be derived from the examples:

  1. A single pair () of parentheses is valid, see examples 4 and 5.
  2. valid parentheses enclosed in a pair of parentheses (...) are valid, see examples 1 and 3
  3. The concatenation of valid parentheses is valid (...)(...), see example 2.

The task can be solved with a single regular expression that contains a code block, here with some comments:

m{
    (               # L1
        (?:         # L2
            \(      # L3
            (?1)?   # L4
            \)      # L5
        )+          # L6
    )               # L7
    (?{length($1) > $max and $max = length($1)})    # L8
    (*FAIL)         # L9
}x;

The block L1 - L7 has two functions:

  1. It captures valid parentheses via $1, see L8.
  2. It specifies a sub-pattern matching valid parentheses via (?1), see L4.

L2 - L6 matches one or more repetitions of the containing sub-expression (rule 3).

L3 - L5 match a pair of parentheses containing zero or one valid parentheses (rules 1 and 2).

L8 checks the length of the current match against the current maximum length.

L9 forces the regex engine into backtracking to find all valid parentheses.

use strict;
use warnings;

sub longest_parenthesis {
    my $max = 0;
    shift =~ m{
        (
            (?:
                \(
                (?1)?
                \)
            )+
        )
        (?{length($1) > $max and $max = length($1)})
        (*FAIL)
    }x;
    $max;
}

See the full solution to task 1.

Task 2: Magic Expression

Submitted by: Mohammad Sajid Anwar


You are given a string containing only digits and a target integer.

Write a script to insert binary operators +, - and * between the digits in the given string that evaluates to target integer.

Example 1

Input: $str = "123", $target = 6
Output: ("1*2*3", "1+2+3")

Example 2

Input: $str = "105", $target = 5
Output: ("1*0+5", "10-5")

Example 3

Input: $str = "232", $target = 8
Output: ("2*3+2", "2+3*2")

Example 4

Input: $str = "1234", $target = 10
Output: ("1*2*3+4", "1+2+3+4")

Example 5

Input: $str = "1001", $target = 2
Output: ("1+0*0+1", "1+0+0+1", "1+0-0+1", "1-0*0+1", "1-0+0+1", "1-0-0+1")

Solution

The rules for valid expression can be derived from the examples:

It is possible to generate all possible expression, discard invalid ones, evaluate and compare them to the target. There are \(4^{n-1}\) expressions to be processed. This doesn’t look desirable but no better approach crossed my mind.

Some implementation details:

use strict;
use warnings;
use Set::Product 'product';
use List::Gather;
use Math::Pari 'PARI';
use experimental 'signatures';

sub magic_expression ($str, $target) {
    state $op = ['', '+', '-', '*'];
    my ($n, $t, $nz, $e) = (length($str), PARI($target), $str !~ /0./);
    return $str == $target ? $str : () if $n == 1;
    my @expr = split /()/, $str;
    my @odd = map 2 * $_ + 1, 0 .. $n - 2;

    gather {
        product {
            @expr[@odd] = @_;
            $e = join '', @expr;
            ($nz || $e !~ /(?<!\d)0\d/) && $t == PARI($e) && take $e;
        } ($op) x ($n - 1);
    }
}

See the full solution to task 2.