When I think about the second task and how it jumbled words, my mind immediately went to Lewis Carroll, and that meant that tonight’s musical theme needed to be Jefferson Airplane’s White Rabbit.
So let’s feed your head on Perl Weekly Challenge 289!
Task 1: Third Maximum
You are given an array of integers, @ints
.
Write a script to find the third distinct maximum in the given array. If third maximum doesn’t exist then return the maximum number.
Example 1
Input: @ints = (5, 6, 4, 1)
Output: 4
The first distinct maximum is 6.
The second distinct maximum is 5.
The third distinct maximum is 4.
Example 2
Input: @ints = (4, 5)
Output: 5
In the given array, the third maximum doesn't exist therefore returns the maximum.
Example 3
Input: @ints = (1, 2, 2, 3)
Output: 1
The first distinct maximum is 3.
The second distinct maximum is 2.
The third distinct maximum is 1.
Approach
There are a bunch of ways to approach this. I could loop through the numbers and keep track of the top three, and return the lowest of them if there are three and the highest if there isn’t. But I’m opting for the “lazy” solution because laziness is a virtue of Perl programmers. I’m making the list of integers unique, and then if there are three or more unique values, sorting them and taking the third from the last value. If there are two or one unique values, I return the maximum value.
Raku
I’m using a Raku Bag to make my list of integers unique.
sub thirdMaximum(@ints) {
my %distinct = @ints.Bag; # get distinct integers
if (%distinct.elems < 3) {
# not enough values for a third max, return max
return %distinct.keys.max();
}
# sort and then get the third from the end
return( (%distinct.keys.sort)[*-3] );
}
View the entire Raku script for this task on GitHub.
$ raku/ch-1.raku
Example 1:
Input: @arr = (5, 6, 4, 1)
Output: 4
Example 2:
Input: @arr = (4, 5)
Output: 5
Example 3:
Input: @arr = (1, 2, 2, 3)
Output: 1
Perl
In Perl, I’m just using a hash to track unique integers, since Perl doesn’t have a Bag.
use List::AllUtils qw( max );
sub thirdMaximum(@ints) {
my %distinct; $distinct{$_}=1 for @ints; # get distinct integers
if ((keys %distinct) < 3) {
# not enough values for a third max, return max
return max(keys %distinct);
}
# sort and then get the third from the end
return( (sort keys %distinct)[-3] );
}
View the entire Perl script for this task on GitHub.
Python
Once again, the Counter
datatype in the collections module is essentially the same as a Bag.
from collections import Counter
def thirdMaximum(ints):
distinct = Counter(ints) # get distinct integers
if len(list(distinct)) < 3:
# not enough values for a third max, return max
return max(list(distinct))
# sort and then get the third from the end
return sorted(list(distinct))[-3]
View the entire Python script for this task on GitHub.
Elixir
I’m going to use the Bag module I wrote for PWC 284. The rest is pretty much usual use of Enum.max/3
, Map.keys/1
, and Enum.sort/2
. The new function I’m using here is Enum.at/3
, which allows us to access a 0-indexed value from a list.
def thirdMaximum(ints) do
distinct = Bag.from_enum(ints) # get distinct integers
if (length(Map.keys(distinct)) < 3) do
# not enough values for a third max, return max
Enum.max(Map.keys(distinct))
else
# sort and then get the third from the end
Map.keys(distinct)
|> Enum.sort(:desc)
|> Enum.at(2)
end
end
View the entire Elixir script for this task on GitHub.
Task 2: Jumbled Letters
An Internet legend dating back to at least 2001 goes something like this:
Aoccdrnig to a rscheearch at Cmabrigde Uinervtisy, it deosn’t mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht the frist and lsat ltteer be at the rghit pclae. The rset can be a toatl mses and you can sitll raed it wouthit porbelm. Tihs is bcuseae the huamn mnid deos not raed ervey lteter by istlef, but the wrod as a wlohe.
This supposed Cambridge research is unfortunately an urban legend. However, the effect has been studied. For example—and with a title that probably made the journal’s editor a little nervous—Raeding wrods with jubmled lettres: there is a cost by Rayner, White, et. al. looked at reading speed and comprehension of jumbled text.
Your task is to write a program that takes English text as its input and outputs a jumbled version as follows:
- The first and last letter of every word must stay the same
- The remaining letters in the word are scrambled in a random order (if that happens to be the original order, that is OK).
- Whitespace, punctuation, and capitalization must stay the same
- The order of words does not change, only the letters inside the word
So, for example, “Perl” could become “Prel”, or stay as “Perl,” but it could not become “Pelr” or “lreP”.
I don’t know if this effect has been studied in other languages besides English, but please consider sharing your results if you try!
Approach
Some parts of this are straightforward: splitting the input text on whitespace into an array, and processing the array in place to preserve the word order. But the bit about punctuation needing to stay the same requires some thought. When punctuation appears at the end of a word, it’s easy, but when it appears within the word, we want to keep it in the right place. In the example test there’s only one word with embedded punctuation: “doesn’t”. It could become “dnseo’t”, “deosn’t”, or “deons’t”. But I could imagine words like “mother-in-law” or “shouldn’t’ve”.
I think what I’ll do is split the word into and array of characters, make a copy of the array that filters out the punctuation, then shift and pop the first and last characters to preserve them and shuffle the remaining characters, and then put the first and last characters back in. Finally, I’ll loop over the original character array, and if I’m looking at a letter, I’ll pull the next letter off the shuffled array. To make this easier to encapsulate, I’ll make rearranging a single word it’s own function.
Raku
The big thing here is the .pick
routine on Raku’s List
class. It can be used to return a single random element from the list, or the entire list shuffled. I’m also using Unicode properties to determine which characters are letters or not.
sub rearrange($word is copy) {
return $word unless $word.chars > 2;
my @chars = $word.comb; # break word into characters
my @letters = @chars.grep(/ <:L> /); # only letters
my $first = @letters.shift; # remove the first letter
my $last = @letters.pop; # remove the last letter
@letters .= pick(*); # shuffle the remaining letters
@letters.unshift($first); # put the first letter back
@letters.push($last); # put the last letter back
# reassemble the word
$word = '';
# loop over the char list
for 0 .. @chars.end -> $i {
if ( @chars[$i] ~~ / <:L> / ) {
# if it's a letter, pull it's replacement
# off the @letters array
$word ~= @letters.shift;
}
else {
# otherwise, it's punctuation, so
# leave it alone
$word ~= @chars[$i];
}
}
return $word;
}
sub jumbleLetters($text) {
my @words = $text.comb(/\S+/);
for @words <-> $word {
$word = rearrange($word);
}
return @words.join(" ");
}
View the entire Raku script for this task on GitHub.
$ raku/ch-2.raku
Example 1:
Input:
According to research at Cambridge University, it doesn't matter
in what order the letters in a word are, the only important thing
is that the first and last letter be at the right place. The rest
can be a total mess and you can still read it without problem.
This is because the human mind does not read every letter by
itself, but the word as a whole.
Output:
Ardccnoig to rscraeeh at Ciragdmbe Utnivseiry, it dsoen't matter
in waht oerdr the letters in a wrod are, the olny itopanmrt tnihg
is taht the frsit and last lteetr be at the right palce. The rest
can be a ttoal mess and you can stlil read it whoutit plerobm.
Tihs is becsaue the human mind does not raed eervy ltteer by
istlef, but the wrod as a wlohe.
Example 2:
Input:
Perl Weekly Challenge
Output:
Perl Weekly Clenghale
Example 3:
Input:
’Twas brillig, and the slithy toves Did gyre and gimble in the
wabe: All mimsy were the borogoves, And the mome raths outgrabe.
Output:
’Twas brlilig, and the slthiy tevos Did grye and gbmile in the
wbae: All mmsiy wree the boeorvogs, And the mmoe raths oragutbe.
Example 4:
Input:
My mother-in-law says I shouldn't've have used so many
parentheticals (but I completely disagree!).
Output:
My mheonl-ri-taw says I stdhuon'v'le have uesd so mnay
pretteclaianhs (but I cllpemetoy dsrageie!).
Perl
To shuffle around the letters, I’m using the shuffle
routine from List::AllUtils
.
use List::AllUtils qw( shuffle );
use Text::Wrap;
sub rearrange($word) {
return $word unless length($word) > 2;
my @chars = split //, $word; # break word into characters
my @letters = grep {/\p{L}/} @chars; # only letters
my $first = shift @letters; # remove the first letter
my $last = pop @letters; # remove the last letter
@letters = shuffle @letters; # shuffle the remaining letters
unshift @letters, $first; # put the first letter back
push @letters, $last; # put the last letter back
# reassemble the word
$word = '';
# loop over the char list
foreach my $i (0 .. $#chars) {
if ( $chars[$i] =~ /\p{L}/ ) {
# if it's a letter, pull it's replacement
# off the @letters array
$word .= shift @letters;
}
else {
# otherwise, it's punctuation, so
# leave it alone
$word .= $chars[$i];
}
}
return $word;
}
sub jumbleLetters($text) {
my @words = split /\s+/, $text;
foreach my $word (@words) {
$word = rearrange($word);
}
return join(" ", @words);
}
View the entire Perl script for this task on GitHub.
Python
from textwrap import fill
from random import shuffle
from regex import match
def rearrange(word):
if len(word) < 3:
return word
chars = [ c for c in word ] # break word into characters
letters = list(filter(lambda c: match(r"\p{L}", c), chars)) # only letters
first = letters.pop(0); # remove the first letter
last = letters.pop(-1); # remove the last letter
shuffle(letters) # shuffle the remaining letters
letters.insert(0, first) # put the first letter back
letters.append(last) # put the last letter back
# reassemble the word
word = ''
# loop over the char list
for i in range(len(chars)):
if match(r"\p{L}", chars[i]):
# if it's a letter, pull it's replacement
# off the @letters array
word += letters.pop(0)
else:
# otherwise, it's punctuation, so
# leave it alone
word += chars[i]
return word
def jumbleLetters(text):
words = text.split()
words[:] = [ rearrange(word) for word in words ]
return " ".join(words)
View the entire Python script for this task on GitHub.
Elixir
Once again, I need an external module to make this work. This time, the module I needed was to do the text wrapping for the paragraphs, and I found Excribe. Using the Mix.install/2 function, I was able to load the library, have it installed on my first run, and available for subsequent runs.
def reassemble([], _, word), do: word
def reassemble(, letters, word) do
cond do
Regex.match?(~r/\p{L}/u, c) ->
# if it's a letter, pull it's replacement
# off the letters array
{next, letters} = List.pop_at(letters, 0)
reassemble(remaining, letters, word <> next)
true ->
# otherwise, it's punctuation, so
# leave it alone
reassemble(remaining, letters, word <> c)
end
end
def rearrange(word) when length(word) < 3, do: word
def rearrange(word) do
chars = String.codepoints(word) # break word into characters
letters = Enum.filter(
chars,
fn c -> Regex.match?(~r/\p{L}/u, c) end # only letters
)
{first, letters} = List.pop_at(letters, 0) # remove the first letter
{last, letters} = List.pop_at(letters,-1) # remove the last letter
letters = Enum.shuffle(letters) # shuffle the remaining letters
|> List.insert_at(0, first) # put the first letter back
|> List.insert_at(-1, last) # put the last letter back
# reassemble the word
reassemble(chars, letters, "")
end
def jumbleLetters(text) do
String.split(text)
|> Enum.map(fn word -> rearrange(word) end)
|> Enum.join(" ")
end
$ elixir/ch-2.exs
Resolving Hex dependencies...
Resolution completed in 0.014s
New:
excribe 0.1.1
* Getting excribe (Hex package)
==> excribe
Compiling 1 file (.ex)
Generated excribe app
Example 1:
Input:
According to research at Cambridge University, it doesn't matter
in what order the letters in a word are, the only important thing
is that the first and last letter be at the right place. The rest
can be a total mess and you can still read it without problem.
This is because the human mind does not read every letter by
itself, but the word as a whole.
Output:
Acconrdig to rrecsaeh at Cgrabimde Useinvrity, it dsnoe't matetr
in what oerdr the lttrees in a wrod are, the olny ipornmatt thnig
is taht the fsrit and last ltteer be at the rihgt plcae. The rest
can be a taotl mses and you can slitl read it wtuihot poerblm.
This is buecase the hamun mnid does not read eevry ltteer by
iesltf, but the word as a wlohe.
Example 2:
Input:
Perl Weekly Challenge
Output:
Perl Welkey Caelhlnge
Example 3:
Input:
’Twas brillig, and the slithy toves Did gyre and gimble in the
wabe: All mimsy were the borogoves, And the mome raths outgrabe.
Output:
’Taws bilrlig, and the shtily tveos Did grye and gblmie in the
wbae: All msimy wree the bvooogres, And the mmoe rhats obtaruge.
Example 4:
Input:
My mother-in-law says I shouldn't've have used so many
parentheticals (but I completely disagree!).
Output:
My mhtrin-la-eow says I svntohd'u'le hvae used so mnay
phaeclntetiras (but I cleeopmlty dgsrieae!).
View the entire Elixir script for this task on GitHub.
Here’s all my solutions in GItHub: https://github.com/packy/perlweeklychallenge-club/tree/master/challenge-289/packy-anderson