Perl Weekly Challenge 146: Prime Numbers and Fraction Tree
These are some answers to the Week 146 of the Perl Weekly Challenge organized by Mohammad S. Anwar.
Spoiler Alert: This weekly challenge deadline is due in a few days from now (on January 9, 2022 at 24:00). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.
Task 1: 10001st Prime Number
Write a script to generate the 10001st prime number.
10001st Prime Number in Raku
Raku has a fast is-prime subroutine or method, using the Miller-Rabin algorithm, to test whether a integer is prime. We can just generate an infinite (lazy) list of prime numbers and look for the 10000st one.
use v6;
my @primes = grep { .is-prime }, (1, 2, 3, -> $a { $a + 2} ...Inf);
say @primes[10001 - 1]; # Subtract 1 because the array starts at 0
This script displays the following output:
$ raku ./10001prime.raku
104743
The Raku script is so simple that it can be implemented as a short Raku one-liner:
$ ./raku -e 'say (grep { .is-prime }, 1..Inf)[10000]'
104743
10001st Prime Number in Perl
Since Perl doesn’t have a built-in is-prime
subroutine, we implement our own. As finding the first 10001 primes is an intensive computation, we implement three performance optimizations compared to the naive brute-force solution: first, with the exception of 2, we only check odd integers. Second, we limit the tested divisors to the square root of the integer being checked. Finally, rather than testing all possible divisors, we test only the primes that we have already found. With these three optimizations, the script runs in less than 0.2 second, much faster than I expected.
use strict;
use warnings;
use feature "say";
use constant MAX => 10_001;
sub primes {
my $max = shift;
my @primes = (2, 3, 5);
my $count = 3;
my $candidate = $primes[-1];
while ($count <= $max) {
$candidate += 2;
my $not_prime = 0;
my $sq_cand = sqrt $candidate;
for my $i (@primes) {
$not_prime = 1, last unless $candidate % $i;
last if $i > $sq_cand;
}
next if $not_prime;
push @primes, $candidate;
$count ++;
}
return $primes[$max-1];
}
my $p = primes(MAX);
say "$p";
This script displays the following output:
$ time perl ./10001prime.pl
104743
real 0m0,192s
user 0m0,124s
sys 0m0,077s
10001st Prime Number in Ring
I continue my exploration of Ring, a recent programming language (the first version was issued in 2016). The Ring implementation below is essentially a port of the Perl program above (with same performance optimizations):
p = primes(10001)
see p + nl
func primes max
primes = [2, 3, 5]
count = len(primes)
candidate = primes[count]
while count < max
candidate += 2
is_prime = True
sqrt_cand = sqrt(candidate)
for i in primes
if candidate % i = 0
is_prime = False
exit
ok
if i > sqrt_cand exit ok
next
if is_prime
add(primes, candidate)
count ++
ok
end
return primes[max]
This program displays the following output:
$ ring ./10001prime.ring
104743
Note that Ring lists start with subscript 1, so we use index 10001 for finding the 10001 ptime number.
10001st Prime Number in Julia
function getprimes(max)
primes = [2, 3, 5]
count = 3
candidate = 5
while (count <= max)
candidate += 2
not_prime = false
sq_cand = sqrt(candidate)
for i in primes
if (candidate % i == 0)
not_prime = true
break
end
i > max && break
end
not_prime && continue
push!(primes, candidate)
count += 1
end
return primes[max] # Julia arrays start with index 1
end
p = getprimes(10001)
println(p)
This program displays the following output:
$ julia 10001prime.jl
104743
Task 2: Curious Fraction Tree
Consider the following Curious Fraction Tree:
You are given a fraction, member of the tree created similar to the above sample.
Write a script to find out the parent and grandparent of the given member.
Example 1:
Input: $member = '3/5';
Output: parent = '3/2' and grandparent = '1/2'
Example 2:
Input: $member = '4/3';
Output: parent = '1/3' and grandparent = '1/2'
The first problem is to understand how this tree is constructed.
Each fraction has two children. It is easy to see that the left child is always a number smaller than 1 and the right child is always a number larger than 1. But how are the values generated?
Well, it is probably not so difficult to find out, but I must confess that I did not try very hard to find the constructing rules, but found them on the Curious Fraction Tree Web page. They are explained in this diagram:
In other words, we have the following rules:
- For any node, the left child is less than 1 et the right child larger than 1;
- For node
a/b
, left child isa/(a+b)
and right node is(a+b)/b
.
From these two rules, it is easy to derive the reciprocal rules for finding the parent of a node:
- For a node
x/y
with a fraction less than 1, the parent isx/(y-x)
; - For a node
x/y
with a fraction larger than 1, the parent is(x-y)/x
.
It is now very easy to implement these rules in various programming languages.
There is, however, one little last problem. The task specifications do not tell us what to do about invalid input. For example, if the input is (1, 1)
(the root to the tree), we will not be able to find a parent. Similarly, if we are given (1/2)
as input, we will find (1, 1)
as the parent, and will not be able to the grand-parent. We would have a problem if one of the values is 0. It is fairly easy to detect these problems, but I still wouldn’t know what to do about it. In many of the implementations below, I have decided that we must be given a valid input and will not try to validate the input. In some others, I have decided to detect some of these invalid input problems and tried to do something reasonable about them.
Curious Fraction Tree in Raku
We’re just implementing the rules above. Here, I do not try to validate the input.
Note that I was first tempted to implement the fraction as a Rat
type of value, since it implements it as a numerator/denominator pair. I finally decided not to do that (because there may be some cases where the fraction might be reduced to their simplest form, division by 0 errors, and other such problems). So I decided to implement the fractions as integer pairs.
use v6;
# for a node x/y less than 1, parent is x/(y-x)
# for a node x/y larger than 1, parent is (x-y)/x
sub parent (\num, \denom) {
return num < denom ?? (num, denom - num) !! (num - denom, denom);
}
for (5, 2), (2, 5), (3, 4), (3, 5) -> $fraction {
my $parent = parent |$fraction[0,1];
my $gd-parent = parent |$parent[0,1];
say "for child $fraction, parent is $parent and gd-parent is $gd-parent";
}
With the test fractions provided in the code, this program displays the following output:
raku ./fraction-tree.raku
for child 5 2, parent is 3 2 and gd-parent is 1 2
for child 2 5, parent is 2 3 and gd-parent is 2 1
for child 3 4, parent is 3 1 and gd-parent is 2 1
for child 3 5, parent is 3 2 and gd-parent is 1 2
Curious Fraction Tree in Perl
This program implements the rules described above. Here, we have made some effort to validate the input, just as an example: we write an error value if we get pas the root node.
use strict;
use warnings;
use feature "say";
# for a node x/y less than 1, parent is x/(y-x)
# for a node x/y larger than 1, parent is (x-y)/x
sub parent {
my ($num, $denom) = @{$_[0]};
return ["Error"] if $num == $denom;
return $num < $denom ? [$num, $denom - $num] : [$num - $denom, $denom];
}
for my $fraction ([5, 2], [2, 5], [3, 4], [3, 5], [2, 1], [1, 1]) {
die "Invalid input node @$fraction" if $$fraction[0] == $$fraction[1];
my $parent = parent $fraction;
my $gd_parent = parent $parent;
say "for child @$fraction, parent is @$parent and gd-parent is @$gd_parent";
}
This program displays the following output:
$ perl ./fraction-tree.pl
for child 5 2, parent is 3 2 and gd-parent is 1 2
for child 2 5, parent is 2 3 and gd-parent is 2 1
for child 3 4, parent is 3 1 and gd-parent is 2 1
for child 3 5, parent is 3 2 and gd-parent is 1 2
for child 2 1, parent is 1 1 and gd-parent is Error
Invalid input node 1 1 at fraction-tree.pl line 15.
Curious Fraction Tree in Some Other Programming Languages
In Ring
Again the same rules as before, with an attempt to handle gracefully some of the exceptions:
# for a node x/y less than 1, parent is x/(y-x)
# for a node x/y larger than 1, parent is (x-y)/x
for test in [ [5, 2], [2, 5], [3, 4], [3,5], [6, 2], [1, 2] ]
parent = find_parent(test[1], test[2])
gd_parent = find_parent(parent[1], parent[2])
see "Node " + to_str(test) + " has parent " + to_str(parent) +
" and grand-parent " + to_str(gd_parent) + nl
next
func find_parent num, denom
if num < denom
return [num, denom - num]
but denom < num
return [num - denom, denom]
else
return ["Error", ""]
ok
func to_str input
return "" + input[1] + " " + input[2]
This script displays the following output:
$ ring ./fraction-tree.ring
Node 5 2 has parent 3 2 and grand-parent 1 2
Node 2 5 has parent 2 3 and grand-parent 2 1
Node 3 4 has parent 3 1 and grand-parent 2 1
Node 3 5 has parent 3 2 and grand-parent 1 2
Node 6 2 has parent 4 2 and grand-parent 2 2
Node 1 2 has parent 1 1 and grand-parent Error
In Python
No attempt here to validate the input.
# for a node x/y less than 1, parent is x/(y-x)
# for a node x/y larger than 1, parent is (x-y)/x
def find_parent(num, denom):
return [num, denom - num] if num < denom else [num - denom, denom]
for test in ([5, 2], [2, 5], [3, 4], [3, 5]):
parent = find_parent(test[0], test[1])
gd_parent = find_parent(parent[0], parent[1])
print("Node", test, "has parent", parent, "and grand-parent", gd_parent)
Output:
$ python3 ./fraction-tree.py
Node [5, 2] has parent [3, 2] and grand-parent [1, 2]
Node [2, 5] has parent [2, 3] and grand-parent [2, 1]
Node [3, 4] has parent [3, 1] and grand-parent [2, 1]
Node [3, 5] has parent [3, 2] and grand-parent [1, 2]
In Julia
Limited attempt to validate the input (catching only some of the exceptions).
function find_parent(num, denom)
return num < denom ? [num, denom - num] :
num > denom ? [num - denom, denom] :
("Error on node $num $denom");
end
for test in [ [5, 2], [2, 5], [3, 4], [3, 5], [1, 2] ]
parent = find_parent(test[1], test[2])
gd_parent = find_parent(parent[1], parent[2])
println("Node $test has parent $parent and grand-parent $gd_parent")
end
Output:
# Node [5, 2] has parent [3, 2] and grand-parent [1, 2]
# Node [2, 5] has parent [2, 3] and grand-parent [2, 1]
# Node [3, 4] has parent [3, 1] and grand-parent [2, 1]
# Node [3, 5] has parent [3, 2] and grand-parent [1, 2]
# Node [1, 2] has parent [1, 1] and grand-parent Error on node
In Awk:
# Run for example as:
# echo ' 5/2
# 2/5
# 3/5' | awk -f fraction-tree.awk
function parent()
{
if (a < b) {
b = b - a
} else {
a = a - b
}
}
BEGIN {
a = 0
b = 0
FS = "/"
}
{
a = $1
b = $2
printf "Node = %d/%d: ", a, b
parent()
printf "Parent = %d/%d; ", a, b
parent()
printf "Grand-parent = %d/%d\n", a, b
}
Output:
$ echo ' 5/2
2/5
3/4
3/5
6/2 ' | awk -f fraction-tree.awk
Node = 5/2: Parent = 3/2; Grand-parent = 1/2
Node = 2/5: Parent = 2/3; Grand-parent = 2/1
Node = 3/4: Parent = 3/1; Grand-parent = 2/1
Node = 3/5: Parent = 3/2; Grand-parent = 1/2
Node = 6/2: Parent = 4/2; Grand-parent = 2/2
In Ruby
# For a node `x/y` with a fraction less than 1, the parent is `x/(y-x)`;
# For a node `x/y` with a fraction larger than 1, the parent is `(x-y)/x`.
def get_parent (pair)
num = pair[0]
denom = pair[1]
return num < denom ? [num, denom - num] : [num - denom, denom];
end
tests = [ [5, 2], [2, 5], [3, 4], [3,5] ]
for test in tests
parent = get_parent(test)
gd_parent = get_parent(parent)
print("Node #{test} - Parent: #{parent} - Grand-Parent: #{gd_parent}\n")
end
Output:
Node 5,2 - Parent: 3,2 - Grand-Parent: 1,2
Node 2,5 - Parent: 2,3 - Grand-Parent: 2,1
Node 3,4 - Parent: 3,1 - Grand-Parent: 2,1
Node 3,5 - Parent: 3,2 - Grand-Parent: 1,2
In Lua
-- For a node `x/y` with a fraction less than 1, the parent is `x/(y-x)`;
-- For a node `x/y` with a fraction larger than 1, the parent is `(x-y)/x`.
local function get_parent(pair)
num = pair[1]
denom = pair[2]
-- no ternary operator in Lua, we can simulate it with and / or:
return num < denom and {num, denom - num} or {num - denom, denom}
end
local function to_str(pair)
-- return pair[1] .. "/" .. pair[2]
return table.concat(pair, "/")
end
local tests = { {5, 2}, {2, 5}, {3, 4}, {3,5} }
for _, test in pairs(tests) do
parent = get_parent(test)
gd_parent = get_parent(parent)
print("Node " .. to_str(test) .. " - Parent: " .. to_str(parent)
.. " - Grand-Parent: " .. to_str(gd_parent))
end
Output:
$ lua ./fraction-tree.lua
Node 5/2 - Parent: 3/2 - Grand-Parent: 1/2
Node 2/5 - Parent: 2/3 - Grand-Parent: 2/1
Node 3/4 - Parent: 3/1 - Grand-Parent: 2/1
Node 3/5 - Parent: 3/2 - Grand-Parent: 1/2
In Kotlin
fun find_parent(pair: IntArray): IntArray {
val num = pair[0]
val denom = pair[1]
return if (num < denom) intArrayOf(num, denom - num) else intArrayOf(num - denom, denom)
}
fun n2str (pair: IntArray): String {
return "${pair[0]}/${pair[1]}"
}
fun main() {
val tests = arrayOf(intArrayOf(5,2), intArrayOf(2,5), intArrayOf(3,4))
for (test in tests) {
val parent = find_parent(test)
val gd_parent = find_parent(parent)
print(n2str(test) + " - Parent: " + n2str(parent))
println(" - Grand-parent: " + n2str(gd_parent))
}
}
Output:
5/2 - Parent: 3/2 - Grand-parent: 1/2
2/5 - Parent: 2/3 - Grand-parent: 2/1
3/4 - Parent: 3/1 - Grand-parent: 2/1
In C
#include <STDIO>
void parent(int *a, int *b) {
if (*a < *b) {
*b -= *a;
} else {
*a -= *b;
}
}
int main (void) {
int num, denom;
while (scanf ("%d/%d", &num, &denom) == 2) {
printf("%d/%d - ", num, denom);
parent(&num, &denom);
printf("Parent: %d/%d - ", num, denom);
parent(&num, &denom);
printf("Grand-parent: %d/%d \n", num, denom);
}
return (0);
}
Output:
$ echo ' 5/2
2/5
3/4
3/5' | ./a.out
5/2 - Parent: 3/2 - Grand-parent: 1/2
2/5 - Parent: 2/3 - Grand-parent: 2/1
3/4 - Parent: 3/1 - Grand-parent: 2/1
3/5 - Parent: 3/2 - Grand-parent: 1/2
In Scala
object fraction_tree extends App {
def findParent(pair: List[Int]): List[Int] = {
val num = pair(0)
val denom = pair(1)
return if (num < denom) List(num, denom - num) else List(num - denom, denom)
}
def n2str(pair: List[Int]): String = {
return s"${pair(0)}" + "/" + s"${pair(1)}"
}
val tests: List[List[Int]] = List(List(5, 2), List(2, 5), List(3, 4))
for (test <- tests) {
val parent = findParent(test)
val gd_parent = findParent(parent)
println(n2str(test) + " - Parent: " + n2str(parent) + " - Grand-parent: " + n2str(gd_parent))
}
}
Output:
5/2 - Parent: 3/2 - Grand-parent: 1/2
2/5 - Parent: 2/3 - Grand-parent: 2/1
3/4 - Parent: 3/1 - Grand-parent: 2/1
Wrapping up
The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on January 16, 2022. And, please, also spread the word about the Perl Weekly Challenge if you can.
Leave a comment