Perl Weekly Challenge: Week 180

Challenge 1:

First Unique Character

You are given a string, $s.

Write a script to find out the first unique character in the given string and print its index (0-based).

Example 1
Input: $s = "Perl Weekly Challenge"
Output: 0 as 'P' is the first unique character
Example 2
Input: $s = "Long Live Perl"
Output: 1 as 'o' is the first unique character

The basic algorithm I used to solve this is to make a hash mapping characters to positions in the string. If a character occurs multiple times, the value of its key in the hash will increase. The first character will be the one whose key in the hash has the lowest value. But the wrinkle is that the spec asks for the first unique character. Consider the string 'aabbccd'. The algorithm as described above would give 1 for the last occurrence of 'a' but it should actually give 6 as 'd' is the first unique character.

So I added some code so if a character has already occurred (i.e. it's key in the hash already exists,) it's value is set to infinity guaranteeing it will have the highest value.

my %chars;

One other thing to note is the use of the .antipairs() method. .comb() has been applied to the string converting it into a list of characters. the List classes .pairs() method converts it into a List of Pairs where the first member (the key) is the position in the list and the second member (the value) is the character at that position. .antipairs() works the same way except the members are swapped i.e. the character is the key and the value is the position. I thought .antipairs() made more sense for this particular purpose.

for $s.comb.antipairs -> $c {
    if %chars{$c.key}:exists {
        %chars{$c.key} = ∞
    } else {
        %chars{$c.key} = $c.value;
    }
}

The last line sorts the hash by value and prints the lowest value.

say (%chars.sort({ $^a.value <=> $^b.value})).first.value;

(Full code on Github.)

The spec didn't specify what should happen if there is no unique character in the string. My script just prints infinity.

Here is the Perl version which works the same way though in the absence of .pairs() or .antipairs() an explicit counter of the position has to be held.

my $pos = 0;
for my $c (split //, $s) {
    if (exists $chars{$c}) {
        $chars{$c} = "Inf";
    } else {
        $chars{$c} = $pos;
    }
    $pos++;
}

say $chars{ (sort { $chars{$a} <=> $chars{$b}} keys %chars)[0] };

(Full code on Github.)

Update: I forgot to mention that I had tried to avoid the need for $pos by using each to iterate through the list of characters as I had done in PWC 174 but unfortunately it doesn't work on a list returned by split for some reason. One of Perls many esoteric quirks it seems.

Challenge 2:

Trim List

You are given list of numbers, @n and an integer $i.

Write a script to trim the given list where element is less than or equal to the given integer.

Example 1
Input: @n = (1,4,2,3,5) and $i = 3
Output: (4,5)
Example 2
Input: @n = (9,0,6,2,3,8,5) and $i = 4
Output: (9,6,8,5)

The second task was even easier. The .grep() method was made for this kind of filtering.

@n.grep({ $_ > $i}).join(q{, }).say;

(Full code on Github.)

And it's equally simple in Perl.

say join q{, }, grep { $_ > $i } @n;

(Full code on Github.)