site.base_url }}/">The Bear's Den -->

The Bear's Den

Enter at your own risk

The Greatest Target

Task 1: Greatest English Letter

Submitted by: Mohammad Sajid Anwar


You are given a string, $str, made up of only alphabetic characters [a..zA..Z].

Write a script to return the greatest english letter in the given string.

A letter is greatest if it occurs as lower and upper case. Also letter ‘b’ is greater than ‘a’ if ‘b’ appears after ‘a’ in the English alphabet.

Example 1

Input: $str = 'PeRlwEeKLy'
Output: L

There are two letters E and L that appears as lower and upper.
The letter L appears after E, so the L is the greatest english letter.

Example 2

Input: $str = 'ChaLlenge'
Output: L

Example 3

Input: $str = 'The'
Output: ''

Solution

The task may easily generalized to arbitrary strings and can be solved in a single pass through the string.

use strict;
use warnings;
use Unicode::Normalize;
use List::Util 'reduce';

sub gl {
    my %l;
    uc reduce {
        local $_ = $b;
        my $fc = fc;
        ($l{$fc} |= /\p{Ll}/ | /\p{Lu}/ << 1) == 3 && $fc gt $a ?
            $fc :
            $a;
    } '', split //, NFC shift;
}

See the full solution.

Task 2: Target Array

Submitted by: Mohammad Sajid Anwar


You are given two arrays of integers, @source and @indices. The @indices can only contains integers 0 <= i < size of @source.

Write a script to create target array by insert at index $indices[i] the value $source[i].

Example 1

Input: @source  = (0, 1, 2, 3, 4)
       @indices = (0, 1, 2, 2, 1)
Output: (0, 4, 1, 3, 2)

@source  @indices  @target
0        0         (0)
1        1         (0, 1)
2        2         (0, 1, 2)
3        2         (0, 1, 3, 2)
4        1         (0, 4, 1, 3, 2)

Example 2

Input: @source  = (1, 2, 3, 4, 0)
       @indices = (0, 1, 2, 3, 0)
Output: (0, 1, 2, 3, 4)

@source  @indices  @target
1        0         (1)
2        1         (1, 2)
3        2         (1, 2, 3)
4        3         (1, 2, 3, 4)
0        0         (0, 1, 2, 3, 4)

Example 3

Input: @source  = (1)
       @indices = (0)
Output: (1)

Solution

This week’s challenge led me into a series of failures.

Smoke Test

The examples in the task look too friendly. For a better understanding of the basic principles, here is a set of test cases that dig somewhat deeper. Looking at a target array, we may ask at which position a value had been inserted that arrived at a specific target position.

Taking the indices (0, 0, 1, 1, 2) in several arrangements such that the resulting target’s array at indices (2, 3, 4) represent all six permutations of insertion points (0, 1, 2). The source values are selected such that their first digit represents the step at which they enter the game and their second digit the index where they were inserted.

@source @indices @target
10 21 32 40 51 0 1 2 0 1 40 51 10 21 32
10 21 30 42 51 0 1 0 2 1 30 51 10 42 21
10 20 32 41 51 0 0 2 1 1 20 51 41 10 32
10 20 31 42 51 0 0 1 2 1 20 51 31 42 10
10 21 30 41 52 0 1 0 1 2 30 41 52 10 21
10 20 31 41 52 0 0 1 1 2 20 41 52 31 10

First Attempt: Sort indices

In my first attempt I tried to solve the task by setting the target elements in the insertion indices’ order and in reverse order within the same insertion index.

The simple PDL line

$s->index(cat($i, -sequence($i))->xchg(0, 1)->qsortveci)

looks easy and solves all of the examples from the task and some more, but it is wrong. It fails all smoke tests. (I had constructed these afterwards).

Second Attempt: Efficient sort

The process description “smells” like an insertion sort with an effort of \(\mathcal{O}(n^2)\).

The process’ efficiency could be improved if we were able to efficiently compare the target positions of a pair of “indices” and apply an efficient sort based on the pairwise compare.

Taking an index as the pair \((p, i)\) with \(p\) as the step number and \(i\) as the insertion index, we may compare two indices \((p_1, i_1)\) and \((p_2, i_2)\).

\(p \diagdown i\) \(i_1 < i_2\) \(i_1 = i_2\) \(i_1 > i_2\)
\(p_1 < p_2\) ? \(<\) \(<\)
\(p_1 > p_2\) \(>\) \(>\) ?

Two symmetric cases are problematic:

The relation between such two insertions does not only depend on the steps in between, but also on the steps before as these set up the environment.

Abandoning this approach, too.

Third Attempt: Follow the recipe

So finally I fell back to implementing the given recipe.

There are some pitfalls. The main issues to consider:

To handle the blocks efficiently, two extra arrays are in use: @begin and @end. For each index in the target array @target, these two arrays hold the begin and end indices of the element’s block. The @end array is used to determine the block that has to be shifted when an element shall be inserted at an already filled position.

If the shift causes a hole to be filled, the whole left part receives a new end index. Here the @begin array springs into action as it provides the start index for the block to be updated.

Example

An example, showing the arrays in action with . as empty fields:

@source =  (1, 2, 3, 4, 5, 6)
@indices = (0, 2, 1, 0, 5, 3)

@source  @indices  @target       @begin        @end
1        0         (1)           (1)           (1)
2        2         (1 . 2)       (0 . 2)       (0 . 2)
3        1         (1 3 2)       (0 0 0)       (2 2 2)
4        0         (4 1 3 2)     (0 0 0 0)     (3 3 3 3)
5        5         (4 1 3 2 . 5) (0 0 0 0 . 5) (3 3 3 3 . 5)
6        3         (4 1 3 6 2 5) (0 0 0 0 0 0) (5 5 5 5 5 5)

Here is the implementation:

use strict;
use warnings;
use experimental 'signatures';

sub target_array ($source, $indices) {
    my @source = @$source;
    my (@target, @begin, @end);
    # Loop over indices
    for my $i (@$indices) {
        # Get the corresponding source element
        my $s = shift @source;
        # Shift an existing block and store its new end
        if (defined(my $ei = $end[$i])) {
            @target[$i + 1 .. $ei + 1] = @target[$i .. $ei];
            $end[$i] = $ei + 1;
        }
        # Begin of block: existing or new
        my $b = $begin[$i] // $i;
        # Possibly join a preceding block
        $b = $begin[$b - 1] if $b > 0 && defined $begin[$b - 1];
        # End of block: existing or new
        my $e = $end[$i] // $i;
        # Possibly join a subsequent block
        $e = $end[$e + 1] if defined $end[$e + 1];
        # Update new block begin and end
        @end[$b .. $e] = ($e) x ($e + 1 - $b);
        @begin[$b .. $e] = ($b) x ($e + 1 - $b);
        # Store the current item
        $target[$i] = $s;
    }
    \@target;
}

See the full solution.