Perl Weekly Challenge: Week 207

Challenge 1:

Keyboard Word

You are given an array of words.

Write a script to print all the words in the given array that can be types using alphabet on only one row of the keyboard.

Let us assume the keys are arranged as below:

Row 1: qwertyuiop
Row 2: asdfghjkl
Row 3: zxcvbnm
Example 1
Input: @words = ("Hello","Alaska","Dad","Peace")
Output: ("Alaska","Dad")
Example 2
Input: @array = ("OMG","Bye")
Output: ()

In Raku we can use Set operations to solve this problem.

First we store our keyboaard rows converting them into arrays of characters in the process.

my @rows = < qwertyuiop asdfghjkl zxcvbnm >.map({ $_.comb; });

We also need a list to store the output if any.

my @output;

Now for each word in our list of words (that we got as command-line arguments.)...

for @words -> $word {

...we also convert it into an array of characters. We also lower-case them with .lc for easier comparison. At first I thought about doing the lower casing all at once at the beginning but we need the original word to add to the output so it is better this way.

    my @letters = $word.lc.comb;

We compare the word as list of characters to each of the rows as lists of characters. The funny operator on the second line is the symbol for subset of or equal to.

    for @rows -> $row {
        if @letters ⊆ @$row {

If the word is a subset of a row, or in other words if all of its' characters can be typed on one row of the keyboard, it is added to the output and we move on to the next word.

            @output.push($word);
            last;
        }
    }
}

Once we have all the valid words, we can print them out. The line below is a bit more verbose then necessary because I wanted the output to look like that in the spec.

say q{(}, @output.map({ q{"} ~ $_ ~ q{"} }).join(q{,}), q{)};

(Full code on Github.)

Perl doesn't have set operators so we have to roll our own. The function below takes two array references as parameters; the second is the set that we want to check the first is a subset of. The second parameter is also converted into a hash whose keys are the elements of the array. The values of the hash are set to 1 but they could be any arbitrary value really.

sub isSubset {
    my @subset = @{$_[0]};
    my %set = map { $_ => 1 } @{$_[1]};

Then we grep() through @subset looking for all the characters which are keys in @set. If the total number of these is equal to the length of @subset, we can return a true value (it is indeed a subset) otherwise false. (it is not.)

    return (scalar grep { exists $set{$_} } @subset) == (scalar @subset);
}

Armed with this isSubset() function, the rest of the Perl version becomes a straightforward translation from Raku.

my @rows = map { [split //] } qw/ qwertyuiop asdfghjkl zxcvbnm /;
my @output;

for my $word (@ARGV) {
    my @letters = split //, lc $word;
    for my $row (@rows) {
        if (isSubset(\@letters, $row)) {
            push @output, $word;
            last;
        }
    }
}

say q{(}, (join q{,}, map { q{"} . $_ . q{"} } @output), q{)};

(Full code on Github.)

Challenge 2:

H-Index

You are given an array of integers containing citations a researcher has received for each paper.

Write a script to compute the researcher’s H-Index. For more information please checkout the wikipedia page.

The H-Index is the largest number h such that h articles have at least h citations each. For example, if an author has five publications, with 9, 7, 6, 2, and 1 citations (ordered from greatest to least), then the author’s h-index is 3, because the author has three publications with 3 or more citations. However, the author does not have four publications with 4 or more citations.

Example 1
Input: @citations = (10,8,5,4,3)
Output: 4

Because the 4th publication has 4 citations and the 5th has only 3.
Example 2
Input: @citations = (25,8,5,3,3)
Output: 3

The H-Index is 3 because the fourth paper has only 3 citations.

This was easy.

We have a variable that stores the rolling result.

my $hindex = 0;

Every element of the command-line arguments is compared to the 1-based index of that element. If it less, we don't have enough citations so we stop processing.

for 1 .. @citations.elems -> $h {
    if @citations[$h - 1] < $h {
        last;
    }

if it is greater than or equal, we update the result with the current index.

    $hindex = $h;
}

After we have gone through all the elements, we print the result and that's all it takes.

say $hindex;

(Full code on Github.)

And this is the Perl version which works the same way.

my $hindex = 0;

for my $h (1 .. scalar @ARGV) {
    if ($ARGV[$h - 1] < $h) {
        last;
    }
    $hindex = $h;
}

say $hindex;

(Full code on Github.)