by Arne Sommer

# Named Roots with Raku and Perl

 Published 27. March 2021.

This is my response to the Perl Weekly Challenge #105.

## Challenge #105.1: Nth root

You are given positive numbers `\$N` and `\$k`.

Write a script to find out the `\$Nth` root of `\$k`. For more information, please take a look at the wiki page.

Example: ```Input: \$N = 5, \$k = 248832 Output: 12 Input: \$N = 5, \$k = 34 Output: 2.02 ```
File: nth-root ```#! /usr/bin/env raku unit sub MAIN (:\$N, :\$k, :i(\$iterations) = 10, :v(\$verbose)); #  my \$start = 1; #  \$start++ while (\$start + 1) ** \$N < \$k; #  say ": Start: \$start" if \$verbose; my \$seq := gather #  { take \$start; #  my \$index = 0; # [5a] loop #  { take (1/\$N) * ((\$N - 1) * \$seq[\$index] + (\$k / \$seq[\$index] ** (\$N - 1))); #  \$index++; # [7a] } } say ": ", \$seq[^\$iterations].join(" | ") if \$verbose; say \$seq[\$iterations -1]; #  ```

 I have chosen to use Named arguments this time, as that makes it hard to mix up the order.

 The initial guess, starting at the lowest possible number.

 Then we add one as long as the resulting value (inital guess to the power of `\$N`) is lower than `\$k`.

 Using `gather`/`take` to set up the values as a sequence.

 Start with the initial guess, at index 0 (in the Sequence).

 An eternal loop, which is ok as we have a lazy data structure (because of the `:=` assignment in ).

 Each value is defined like this, depending on the previous value (in `\$seq[\$index]`). We increase the index after computing the value [7a].

 Print the value at the given position in the sequence. This will compute the values up to this one.

See my Raku Gather, I Take article or docs.raku.org/syntax/gather take for more information about `gather`/`take`.

Running it:

```\$ ./nth-root -N=5 -k=248832 12 \$ ./nth-root -N=5 -k=34 2.024397458499885 ```

#### 2.024397458499885 vs 2.02

The second result is not what the challenge specified (i.e. «2.02»), so let us do the math manually (in e.g. REPL modem, so not really manually…):

```\$ raku > say 2.024397458499885 ** 5 # -> 33.99999999999999 > say 2.02 ** 5 # -> 33.6323216032 ```

So we got the correct answer (or at least something very close to it), and the challenge got it wrong.

We can run it with verbose mode:

```\$ ./nth-root -N=5 -k=248832 -v : Start: 11 : 11 | 12.199112 | 12.006394530167233 | 12.00000680774632 | 12.000000000007724 | 12 | 12 | 12 | 12 | 12 12 \$ ./nth-root -N=5 -k=34 -v : Start: 2 : 2 | 2.025 | 2.02439781697 | 2.0243974585000117 | 2.024397458499885 | 2.024397458499885 | 2.024397458499885 | 2.024397458499885 | 2.024397458499885 | 2.024397458499885 2.024397458499885 ```

As you can see from the list of values, both of them arrive at the final answer quite some time before stopping. So adding more iterations would not help us. But we can do so anyway, with the «-i» command line option:

```\$ ./nth-root -N=5 -k=248832 -i=20 -v : Start: 11 : 11 | 12.199112 | 12.006394530167233 | 12.00000680774632 | 12.000000000007724 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 12 ```

An optimisation could be to go on in the sequence, as long as the value changes, and stop when we get two identical values:

File: nth-root-better ```#! /usr/bin/env raku unit sub MAIN (:\$N, :\$k, :i(\$iterations) = 10, :b(\$best), :v(\$verbose)); #  ## my \$start = 1; \$start++ while (\$start + 1) ** \$N < \$k; say ": Start: \$start" if \$verbose; my \$seq := gather { take \$start; my \$index = 0; loop { take (1/\$N) * ( (\$N - 1) * \$seq[\$index] + (\$k / \$seq[\$index] ** (\$N - 1) )); \$index++; } } if \$best #  { my \$index = 0; #  my \$current = \$seq[\$index++]; \$current = \$seq[\$index++] while \$current != \$seq[\$index]; #  say ": ", \$seq[0..\$index].join(" | ") if \$verbose; say \$seq[\$index]; #  } else #  { say ": ", \$seq[^\$iterations].join(" | ") if \$verbose; say \$seq[\$iterations -1]; } ```

 Use the «-b» command line option to get the best value, and no more iterations after that.

 If we have asked for the best value,

 Keep track of the index.

 Go on, as long as the next value differs from the current one.

 Print the last value we computed.

 The old behaviour.

Running it:

```\$ ./nth-root-better -N=5 -k=248832 -b -v : Start: 11 : 11 | 12.199112 | 12.006394530167233 | 12.00000680774632 | 12.000000000007724 | 12 | 12 12 \$ ./nth-root-better -N=5 -k=32 -b -v : Start: 1 : 1 | 7.2 | 5.7623815 | 4.615709792667913 | 3.706668057515866 | 2.9992379973399035 | 2.478483071376254 | 2.152390478860902 | 2.020104202537039 | 2.0003961939397454 | 2.0000001569074692 | 2.0000000000000244 | 2 | 2 2 \$ ./nth-root-better -N=5 -k=34 -b -v : Start: 2 : 2 | 2.025 | 2.02439781697 | 2.0243974585000117 | 2.024397458499885 | 2.024397458499885 2.024397458499885 ```

### A Perl Version

This is a straight forward translation of the last Raku version, as to how it works. I replaced the Sequence with a manually constructed array and a recursive procedure.

File: nth-root-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'signatures'; use feature 'say'; use Getopt::Long; no warnings qw(experimental::signatures); my \$N = 0; my \$k = 0; my \$iterations = 10; my \$best = 0; my \$verbose = 0; GetOptions("N=i" => \\$N, "k=i" => \\$k, "i=i" => \\$iterations, "b" => \\$best, "v" => \\$verbose); my \$start = 1; \$start++ while (\$start + 1) ** \$N < \$k; say ": Start: \$start" if \$verbose; my @seq; \$seq = \$start; sub get_iteration (\$index) #  { unless (defined \$seq[\$index]) { \$seq[\$index] = (1/\$N) * ( (\$N - 1) * get_iteration(\$index -1) + (\$k / get_iteration(\$index -1) ** (\$N - 1) ) ); } return \$seq[\$index] } if (\$best) { my \$index = 0; my \$current = get_iteration(\$index++); \$current = get_iteration(\$index++) while \$current != get_iteration(\$index); # [1a] say ": ", join(" | ", @seq) if \$verbose; #  say \$seq[\$index]; } else { my \$value = get_iteration(\$iterations -1); # [1b] say ": ", join(" | ", @seq) if \$verbose; #  say \$value; } ```

 We use «get_iteration» to get (and possibly calculate) the value with the specified index. It stores the value in the array, so that we only calculate any single value once.

 We array has the calculated values only (obviously), so we can print them all.

Running it gives the same result as the Raku version:

```\$ ./nth-root-perl -N=5 -k=248832 -v : Start: 11 : 11 | 12.199112082508 | 12.0063945301672 | 12.0000068077463 | 12.0000000000077 | 12 | 12 | 12 | 12 | 12 12 \$ ./nth-root-perl -N=5 -k=248832 -v -b : Start: 11 : 11 | 12.199112082508 | 12.0063945301672 | 12.0000068077463 | 12.0000000000077 | 12 | 12 12 ```

## Challenge #105.2: The Name Game

You are given a `\$name`.

Write a script to display the lyrics to the Shirley Ellis song `The Name Game`. Please checkout the wiki page for more information.

Example: ```Input: \$name = "Katie" Output: Katie, Katie, bo-batie, Bonana-fanna fo-fatie Fee fi mo-matie Katie! ```

#### Consonants vs Vowels

The letter «y» is either a consonant or a vowel in English, depending on who you ask. See e.g. www.merriam-webster.com/words-at-play/why-y-is-sometimes-a-vowel-usage. The Raku and Perl programs below regard it as a consonant, unless explicitly told otherwise.

This is pretty straight forward, given the explanation in the Wikipedia article.

File: name-game ```#! /usr/bin/env raku unit sub MAIN (\$x, :y(:\$y-is-a-vowel)); #  my @vowels = \$y-is-a-vowel ?? <a e i o u y> !! <a e i o u>; #  my \$y = \$x.substr(0,1).lc eq any(@vowels) ?? \$x.lc !! \$x.substr(1); #  say #  "\$x, \$x, bo-{ \$x.substr(0,1).lc eq "b" ?? \$y !! "b\$y" } #  Bonana-fanna fo-{ \$x.substr(0,1).lc eq "f" ?? \$y !! "f\$y" } #  Fee fi mo-{ \$x.substr(0,1).lc eq "m" ?? \$y !! "m\$y" } #  \$x!"; #  ```

 Specify the name to use, and optionally «-y» if you want the program to treat «y» as a vowel.

 The vowels, depending on the «-y» command line option.

 The `(Y)` value. If the first letter in the name (the `(X)` value) is a consonant, we remove it. Note the `lc` to turn the name into lower case when we did not remove the inital character (which usually is an upper case letter).

 A multi line string.

 Note the use of embedded code inside `{…}` in strings. If the original name (the `(X)` value) starts with «b» (or «B», as `lc` coerces it lo lower case), we drop the leading «b».

 As , but checking for «f».

 As  and , but checking for «m».

 The last line. Note the final quote to mark the end of the string.

Note that the comments marked with red must be removed from the code, if you want to run it, as they reside inside a (multi line) text string. (And will end up in the ourtput.)

Running it:

```\$ ./name-game Katie Katie, Katie, bo-batie Bonana-fanna fo-fatie Fee fi mo-matie Katie! \$ ./name-game Billy Billy, Billy, bo-illy Bonana-fanna fo-filly Fee fi mo-milly Billy! \$ ./name-game Fred Fred, Fred, bo-bred Bonana-fanna fo-red Fee fi mo-mred Fred! \$ ./name-game Marsha Marsha, Marsha, bo-barsha Bonana-fanna fo-farsha Fee fi mo-arsha Marsha! ```

Let us try a name starting with «Y», which either is a consonant (first run) or a vowel (second run, with «-y»):

```\$ ./name-game Yasmine Yasmine, Yasmine, bo-basmine Bonana-fanna fo-fasmine Fee fi mo-masmine Yasmine! \$ ./name-game -y Yasmine Yasmine, Yasmine, bo-byasmine Bonana-fanna fo-fyasmine Fee fi mo-myasmine Yasmine! ```

Looking good.

### Perl

This is a straight forward translation of the Raku version.

File: name-game-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; use Getopt::Long; use Perl6::Junction 'any'; my \$y_is_a_vowel = 0; GetOptions("y_is_a_vowel" => \\$y_is_a_vowel); my \$x = shift(@ARGV) // die 'Please specify the name'; my @vowels = \$y_is_a_vowel ? qw/a e i o u y/ : qw/a e i o u/; my \$y = lc(substr(\$x,0,1)) eq any(@vowels) ? lc(\$x) : substr(\$x, 1); say "\$x, \$x, bo-", ( lc(substr(\$x,0,1)) eq "b" ? \$y : "b\$y" ); #  say "Bonana-fanna fo-", ( lc(substr(\$x,0,1)) eq "f" ? \$y : "f\$y" ); say "Fee fi mo-", ( lc(substr(\$x,0,1)) eq "m" ? \$y : "m\$y" ); say "\$x!"; ```

 perl does not support code inside strings, so we do it with 4 `say`s, and a list of two arguments (note the comma) for the first three.

Running it gives the same result as the Raku version:

```\$ ./name-game-perl Yasmine Yasmine, Yasmine, bo-basmine Bonana-fanna fo-fasmine Fee fi mo-masmine Yasmine! \$ ./name-game-perl -y_is_a_vowel Yasmine Yasmine, Yasmine, bo-byasmine Bonana-fanna fo-fyasmine Fee fi mo-myasmine Yasmine! ```

And that's it.