Perl Weekly Challenge: Tirhd Mumixam Prel Welkey Caelhlnge

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:

  1. The first and last letter of every word must stay the same
  2. The remaining letters in the word are scrambled in a random order (if that happens to be the original order, that is OK).
  3. Whitespace, punctuation, and capitalization must stay the same
  4. 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

Leave a Reply