Perl Weekly Challenge: Week 315

Challenge 1:

Find Words

You are given a list of words and a character.

Write a script to return the index of word in the list where you find the given character.

Example 1
Input: @list = ("the", "weekly", "challenge")
    $char = "e"
Output: (0, 1, 2)
Example 2
Input: @list = ("perl", "raku", "python")
    $char = "p"
Output: (0, 2)
Example 3
Input: @list = ("abc", "def", "bbb", "bcd")
    $char = "b"
Output: (0, 2, 3)

Although my solution is a little longer, the essence of it is one line. In fact, not even that much because some of the code on that line is for output presentation purposes only.

say q{(}, @list.keys.grep({ @list[$_].contains($char) }).join(q{, }), q{)};

(Full code on Github.)

The heart of it is this: We get the indices of @list with .keys(). Then we filter them with .grep(). The filter is to see if the element with that index contains $char with the appropriately named .contains(). And that's it! The rest of the line is, as I mentioned, just so we can print the filtered list of indices in a nice way.

This is the Perl version. As we don't have .contains(), I replaced it with index(). It is actually for finding the position of a substring in a string but it returns -1 if the substring is not found so it can be used for our purpose.

say q{(}, (join q{, }, grep { index ($list[$_], $char) > -1 } keys @list), q{)};

(Full code on Github.)

Challenge 2:

Find Third

You are given a sentence and two words.

Write a script to return all words in the given sentence that appear in sequence to the given two words.

Example 1
Input: $sentence = "Perl is a my favourite language but Python is my favourite too."
    $first = "my"
    $second = "favourite"
Output: ("language", "too")
Example 2
Input: $sentence = "Barbie is a beautiful doll also also a beautiful princess."
    $first = "a"
    $second = "beautiful"
Output: ("doll", "princess")
Example 3
Input: $sentence = "we will we will rock you rock you.",
    $first = "we"
    $second = "will"
Output: ("we", "rock")

I'll show the Perl version first this time. This seemed like something that could be pretty easily done with a regular expression and indeed /$first\s+$second\s+(\w+))/gx was sufficient to get the correct answers for examples 1 and 2. (the m and s flags are some things I always add as advised by Perl Best Practices but they are not relevant for this purpose.)

Example 3 was a problem though because "we" is both one of the search words and a result. This made the next match start after the second "we" so "rock" didn't get picked up. I'm not sure this is the best solution but I solved this by adding ?= (positive lookahead) to the beginning of the expression. Now it works.

my @output = ($sentence =~ /(?=$first\s+$second\s+(\w+))/gmsx);

This line is just for printing @output in the same way as the examples.

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

(Full code on Github.)

The Raku version gave unexpected difficulty. The equivalent of ?= in Rakus' revamped regular expression engine is <?after>. (There is also <?before>) so I thought I could make the regular expression like this:

/ <?after $first \s+ $second \s+ > (\w+) /

But for some reason you cannot interpolate strings into an <?after> expression even though my reading of the documentation suggests you can. I ended up having to put it in a string and interpolate that like this:

my $preceding = '<?after $first \s+ $second \s+ >';
my @output = $sentence.match(/ <$preceding> (\w+) /, :g);

(Full code on Github.)

And this works. Weird.