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 Pair
s 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;
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] };
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;
And it's equally simple in Perl.
say join q{, }, grep { $_ > $i } @n;