with Raku (and perl)

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

Write a script to generate all

For more information about

In mathematics, a semiprime is a natural number that is the product of exactly two prime numbers. The two primes in the product may equal each other, so the semiprimes include the squares of prime numbers.

Example:

`Semiprime`

number `<= 100`

.
For more information about

`Semiprime`

, please checkout the
wikipedia page.
In mathematics, a semiprime is a natural number that is the product of exactly two prime numbers. The two primes in the product may equal each other, so the semiprimes include the squares of prime numbers.

Example:

10 is Semiprime as 10 = 2 x 5
15 is Semiprime as 15 = 3 x 5

File: semi-prime

#! /usr/bin/env raku
subset PosInt of Int where * > 0;
unit sub MAIN (PosInt $limit = 100); # [1]
my @primes = (1 .. $limit div 2).grep( *.is-prime); # [2]
my @result; # [3]
for @primes -> $a # [4]
{
for @primes -> $b # [5]
{
my $product = $a * $b; # [6]
@result.push: $product if $product <= $limit; # [7]
}
}
say @result.sort.squish.join(", "); # [8]

[1] The upper limit can be overridden.

[2] The highest possible number (divisor) than we can have as half the number, as 2
is a prime (i.e. `x/2 == x/2 * 2`

). So we stop when we have reached the
half way point. Note the use of the integer divison operator `div`

instead
of `, so that we get an integer as result.`

[3] The values will end up here.

[4] Iterate over the primes up to and including the half way point.

[5] Twice, as e.g. 10x10 is a legal solution.

[6] Get the product,

[7] and save it.

[8] Sort the result, get rid of duplicates (as e.g. both 2x3 and 3x2 give us 6), and print the result.

Running it:

$ ./semi-prime
4, 6, 9, 10, 14, 15, 21, 22, 25, 26, 33, 34, 35, 38, 39, 46, 49, 51, 55, 57, \
58, 62, 65, 69, 74, 77, 82, 85, 86, 87, 91, 93, 94, 95
$ ./semi-prime 200
4, 6, 9, 10, 14, 15, 21, 22, 25, 26, 33, 34, 35, 38, 39, 46, 49, 51, 55, 57, \
58, 62, 65, 69, 74, 77, 82, 85, 86, 87, 91, 93, 94, 95, 106, 111, 115, 118, \
119, 121, 122, 123, 129, 133, 134, 141, 142, 143, 145, 146, 155, 158, 159, \
161, 166, 169, 177, 178, 183, 185, 187, 194

#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Math::Prime::Util 'is_prime';
use List::Util 'uniq';
my $limit = 100;
my @primes = grep { is_prime($_) } (1 .. $limit / 2);
my @result;
for my $a (@primes)
{
for my $b (@primes)
{
my $product = $a * $b;
push(@result, $product) if $product <= $limit;
}
}
say join(", ", uniq sort{ $a <=> $b } @result); # [1]

[1] Note that `sort`

lives in a textual world, so we have to tell it that we
are giving it numeric values.

Running it gives the same result as the Raku version:

$ ./semi-prime-perl
4, 6, 9, 10, 14, 15, 21, 22, 25, 26, 33, 34, 35, 38, 39, 46, 49, 51, 55, 57,\
58, 62, 65, 69, 74, 77, 82, 85, 86, 87, 91, 93, 94, 95

You are given two positive numbers,

Write a script to generate

For more information about

The standard Ulam sequence (the (1, 2)-Ulam sequence) starts with U1 = 1 and U2 = 2. Then for n > 2, Un is defined to be the smallest integer that is the sum of two distinct earlier terms in exactly one way and larger than all earlier terms.

Example 1:

`$u`

and `$v`

.
Write a script to generate

`Ulam Sequence`

having at least 10
`Ulam numbers`

where `$u`

and `$v`

are the
first 2 `Ulam numbers`

.
For more information about

`Ulam Sequence`

, please checkout the
website.
The standard Ulam sequence (the (1, 2)-Ulam sequence) starts with U1 = 1 and U2 = 2. Then for n > 2, Un is defined to be the smallest integer that is the sum of two distinct earlier terms in exactly one way and larger than all earlier terms.

Example 1:

Input: $u = 1, $v = 2
Output: 1, 2, 3, 4, 6, 8, 11, 13, 16, 18

Example 2:
Input: $u = 2, $v = 3
Output: 2, 3, 5, 7, 8, 9, 13, 14, 18, 19

Example 3:
Input: $u = 2, $v = 5
Output: 2, 5, 7, 9, 11, 12, 13, 15, 19, 23

Note that the program is somewhat more complicated than necessary, due to the verbose mode.

File: ulam-sequence#! /usr/bin/env raku
subset PosInt of Int where * > 0;
unit sub MAIN (PosInt $u, # [1]
PosInt $v where $v > $u, # [1a]
:l(:$limit) = 10, # [2]
:v(:$verbose));
my $size = $limit.chars;
my $ulam := gather # [3]
{
my @ulam = ($u.Int, $v.Int); # [4]
my $index = 1; # [5]
say ": { indent(1, $size) }: @ulam[0]" if $verbose;
take @ulam[0]; # [6]
say ": { indent(2, $size) }: @ulam[1]" if $verbose;
take @ulam[1]; # [7]
loop # [8]
{
my @sum = @ulam.combinations(2)>>.sum.grep( * > @ulam[$index] ); # [9]
say ": { indent($index + 2, $size) }: { @sum.sort.join(", ") } \
(with duplicates)" if $verbose;
my @bag = @sum.Bag.grep( *.value == 1 ).map( *.key ).sort; # [10]
say ": { ' ' x $size } { @bag.join(", ") } (no duplicates)" if $verbose;
my $min = @bag.min; # [11]
@ulam[++$index] = $min; # [12]
take $min; # [13]
}
}
say $ulam[^$limit].join(", "); # [14]
sub indent ($number, $size)
{
return $number.fmt('%' ~ $size ~ 'd');
}

[1] The next number is larger than the previous one, so we have a sorted list without duplicates. It follows that the second number must be larger than the first one, so we should enforce that.

[2] You do not have to stop at 10.

[3] Setting up the sequence with `gather`

/`take`

is ideal here.

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

/`take`

.

[4] The two first values, coerced to integers just for fun (from the `IntStr`

type, as delivered by the command line).

[5] The index of the last value in the array so far.

[6] Return the first value (at index 0).

[7] Return the second value (at index 1).

[8] The rest of the values, done in a loop.

[9] Get all the combinations of two earlier elements (`combinations(2)`

),
and the sum of each of them (`>>.sum`

). Get rid of the sums that are not
larger than the currently largest value in the sequence (with `grep`

).

[10] Coerce the list to a `Bag`

, a hash like structur where
the values in the list turns ut as keys, and the values are the number of times they
occured. Then we use `grep`

to get rid of elements that does not have a value
of 1 (i.e. get rid of values that occured multiple times). The `sort`

is there
for the verbose output.

See
docs.raku.org/type/Bag
for more information about the `Bag`

type.

[11] Get the smallest value. Note that we could have used `@bag[0]`

here
instead, as the list is sorted.

[12] Add the new value to the list,

[13] and return it.

[14] Print the required number of values.

Running it:

$ ./ulam-sequence 1 2
1, 2, 3, 4, 6, 8, 11, 13, 16, 18
$ ./ulam-sequence 2 3
2, 3, 5, 7, 8, 9, 13, 14, 18, 19
$ ./ulam-sequence 2 5
2, 5, 7, 9, 11, 12, 13, 15, 19, 23

With verbose mode:

```
$ ./ulam-sequence -v 1 2
: 1: 1
: 2: 2
: 3: 3 (with duplicates)
: 3 (no duplicates)
: 4: 4, 5 (with duplicates)
: 4, 5 (no duplicates)
: 5: 5, 5, 6, 7 (with duplicates)
: 6, 7 (no duplicates)
: 6: 7, 7, 8, 9, 10 (with duplicates)
: 8, 9, 10 (no duplicates)
: 7: 9, 9, 10, 10, 11, 12, 14 (with duplicates)
: 11, 12, 14 (no duplicates)
: 8: 12, 12, 13, 14, 14, 15, 17, 19 (with duplicates)
: 13, 15, 17, 19 (no duplicates)
: 9: 14, 14, 14, 15, 15, 16, 17, 17, 19, 19, 21, 24 (with duplicates)
: 16, 21, 24 (no duplicates)
: 10: 17, 17, 17, 18, 19, 19, 19, 20, 21, 22, 24, 24, 27, 29 (with duplicates)
: 18, 20, 21, 22, 27, 29 (no duplicates)
1, 2, 3, 4, 6, 8, 11, 13, 16, 18
```

The fifth value is 6, as the lowest one (5) has been removed by the duplicate rule.

And that's it. (No Perl version of this one, as the duplicate rule ate the program.)