Sign of the Four

Wherein we return to the schoolyard and play a nice game of mystery and surprise… For a change…

THE WEEKLY CHALLENGE – PERL & RAKU #160 Task 1


How often have I said to you that when you have eliminated the impossible, whatever remains, however improbable, must be the truth?

— Sherlock Holmes


Four Is Magic

Submitted by: Mohammad S Anwar

You are given a positive number, $n < 10.

Write a script to generate english text sequence starting with the English cardinal representation of the given number, the word ‘is’ and then the English cardinal representation of the count of characters that made up the first word, followed by a comma. Continue until you reach four.

Example 1:
Input: $n = 5
Output: Five is four, four is magic.
Example 2:
Input: $n = 7
Output: Seven is five, five is four, four is magic.
Example 3:
Input: $n = 6
Output: Six is three, three is five, five is four, four is magic.

ANALYSIS

Right out of the gate, let’s notice that this clever piece of numerological slight-of-hand is not really a math problem. It’s more a piece of stage magic, where a series of actions is designed to produce an unexpected result that baffles the observer. The numbers, if you will, are the essential misdirection. The only math involved is simple counting.

Although the challenge is for values less than ten, the process works for literally any number. We can demonstrate this by looking at the lengths of the written numbers between one and nine. These words have lengths between 3 and 5 characters, allowing only three possible outcomes. Enumerated, these are:

  • Three is five, five is four, four is magic
  • Four is magic
  • Five is four, four is magic

So any other number between 1 and 9, with the addition of one additional step will resolve into one of these three endings.

Extending the range to include the other irregular number names — ten through nineteen — yield only additional possible values also between 3 and 9, and we can see those resolutions have already been accommodated. Likewise the non-compounded words for multiples of ten: twenty, thirty, forty, etc, all have fewer than ten letters as well.

For compounded numbers, these under one hundred will be composed in two parts, each of which will have a less than 10 letters.1 Thus the final tally will be under 20, and hence is known to resolve. This expands the known case range to values under one hundred. Above one hundred the final character count, although larger, will generally be less than 100, and for arbitrarily large values the same reasoning can be scaled upwards to 1000 or 1,000,000 characters with no upper limit. In all cases of numbers greater than four the count of the characters will be less than the value itself.

The cardinal numbers are positive integers, with a special case allowing for null quantities: “zero”, “none” or simply “no”, which, albeit a very interesting idea in itself, really doesn’t enter the picture here. We’ve shown that any quantity of letters will resolve, so extending the range of input to any rational number just adds extra letters to the phrase, depending on how we wish to count them, with the outcome inevitable: “negative one-thousand six-hundred and forty-two point six six nine” is fifty-five, fifty-five is nine, nine is four and four is magic.


1 Although not specifically stated, it is my understanding that in the trick, hyphens and spaces are not sounded out when counting the letters, and hence do not add to the final tally. This provides a cleaner flow when performed, avoiding unnecessary distraction, allowing the *real* distraction, the implied math, to pass unchecked. A little further analysis, however, will show that even if included we are still assured that a conclusion is inevitable. And how would that work, anyway? Would you say “hyphen” or count the six letters in the word for it? In any case it doesn’t, as we said, matter.

METHOD

PERL 5 SOLUTION

To solve the challenge as stated it is fairly straightforward to create a hash table of single digits associated with their English-language names: { 1 => "one, 2 => "two", ... , 9 => "nine" }, translating the counts to written words as required.

But that’s not interesting enough for me. I mean, I did do that, but then moved on. I expanded my word list to include the numbers up to twenty, but no accommodation is made for compound numbers over 20, however.

Note that calling keys on an array now returns an ordered list of the indexes, which as you can see is sometimes quite convenient. We include a name for 0, because it’s clearer what’s going on than the alternative would be — adding 1 to get the proper alignment — at little cost.

my @names = qw( zero 
                one 
                two 
                three
                four
                five
                six
                seven
                eight
                nine
                ten
                eleven
                twelve
                thirteen
                fourteen
                fifteen
                sixteen
                seventeen
                eighteen
                nineteen
                twenty );
                
my %d2n = map { $_ => $names[$_] } keys @names;

my $out;
my $num   = shift @ARGV // 20;

while (1) {
    $out .= "$d2n{$num} is ";
    $out .= "magic" and last if $num == 4 ;
    $out .= $d2n{ length $d2n{$num} } . ', ';
    $num = length $d2n{$num} ;
}

say $out;

I did though want to handle any number, although that would necessitate a fair bit of Natural Language Programming to say the numbers correctly. Rather than reinvent the wheel, we’ll draw on the work of Neil Bowers, who provides us with the excellent Lingua::EN::Numbers to do the translations for us. We do need to add a step to strip out whitespace and hyphens from our number names before we count the characters, but that’s hardly any bother.

The redo statement restarts the block, creating a nice looping construct without the need for some inelegant while (1){...} or similar.

use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';
use Lingua::EN::Numbers qw( num2en );

my $input = shift // -1642.669;
say magic( $input );

sub magic ($num, $out = '') {
    {
        my $name =  num2en($num);
        $out .= "$name is ";
        $name =~ s/[^a-z]//g;
        $num == 4
            ? return ($out . "magic")
            : ($out .= num2en(  length $name  ) . ', ');
        $num = length $name ;
        redo;
    }
}
Raku Solution

In Raku we’ll only do the first twenty numbers. The cardinal number NLP module Lingua::EN::Numbers has been ported over to that language, but I haven’t used it for this demonstration, which works much like the first Perl version above. When translating I build the lookup mapping without the dummy “zero” entry, and was able to use the lovely control statement loop to do what it says. I so much it that to abusing a conditional that will never fail to produce an infinite loop.

unit sub MAIN ( $num is copy = 20 ) ;

my @names = qw< 
    one	        two	    three	four	    five	    
    six	        seven	    eight	nine	    ten	        
    eleven	twelve	    thirteen	fourteen    fifteen	    
    sixteen	seventeen   eighteen	nineteen    twenty	  >;

my %d2n = @names.keys.map: {$_+1 => @names[$_]};
my $out;

loop {
    $out ~= "%d2n{$num} is ";
    $out ~= "magic" and last if $num == 4 ;
    $out ~= %d2n{ %d2n{$num}.chars } ~ ', ';
    $num = %d2n{$num}.chars ;
}

$out.put;


The Perl Weekly Challenge, that idyllic glade wherein we stumble upon the holes for these sweet descents, is now known as

The Weekly Challenge – Perl and Raku

It is the creation of the lovely Mohammad Sajid Anwar and a veritable swarm of contributors from all over the world, who gather, as might be expected, weekly online to solve puzzles. Everyone is encouraged to visit, learn and contribute at

https://theweeklychallenge.org

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s