Two Triplets
with Raku and Perl

by Arne Sommer

Two Triplets with Raku and Perl

[101] Published 6. November 2020.

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

Challenge #085.1: Triplet Sum

You are given an array of real numbers greater than zero.

Write a script to find if there exists a triplet (a,b,c) such that 1 < a+b+c < 2. Print 1 if you succeed otherwise 0.

Example 1
Input: @R = (1.2, 0.4, 0.1, 2.5)
Output: 1 as 1 < 1.2 + 0.4 + 0.1 < 2
Example 2
Input: @R = (0.2, 1.5, 0.9, 1.1)
Output: 0
Example 3
Input: @R = (0.5, 1.1, 0.3, 0.7)
Output: 1 as 1 < 0.5 + 1.1 + 0.3 < 2

Obtaining triplets from an array is easy with combinations(3), where 3 is the number of items in each combination. Here with the values from the first example:

> say (1.2, 0.4, 0.1, 2.5).combinations(3);
((1.2 0.4 0.1) (1.2 0.4 2.5) (1.2 0.1 2.5) (0.4 0.1 2.5))

See docs.raku.org/routine/combinations for more information about combinations.

We need the sum of each triplet, and can get that with sum on each triplet with the hyper operator >> like this:

> say (1.2, 0.4, 0.1, 2.5).combinations(3)>>.sum;
(1.7 4.1 3.8 3)

See docs.raku.org/routine/sum for more information about sum.

See docs.raku.org/language/operators#index-entry-hyper… for more information about Hyper Operators and >>.

We are not interested in any of the sums as such, just the fact that any of them at all satisfy the expression 1 < sum < 2. We can use an any Junction for this:

> say 1 <  any( (1.2, 0.4, 0.1, 2.5).combinations(3)>>.sum ) < 2;
any(True, False, False, False)

The result is a Junction, telling us that the first value is ok, and the last three are not.

See docs.raku.org/routine/any for more information about any, and docs.raku.org/type/Junction for more information about Junctions.

We can collapse the junction to a single Boolean value with the so operator: This will give us True if any of the values are True.

> say so 1 < any( (1.2, 0.4, 0.1, 2.5).combinations(3)>>.sum ) < 2
True

See docs.raku.org/routine/so for more information about the so operator.

The challenge wanted the numbers 1 instead of True, and 0 instead of False. We can achieve that by coercing the Boolean value to a number with the + operator:

> say + so 1 <  any( (1.2, 0.4, 0.1, 2.5).combinations(3)>>.sum ) < 2;
1

See docs.raku.org/routine/+ for more information about the Numeric Context Operator +.

Then the program:

File: triplet-sum
unit sub MAIN (*@R where @R.elems > 0 && all(@R) > 0);

say + triplet-sum(@R);

sub triplet-sum (@numbers)
{
  so 1 < any(@numbers.combinations(3)>>.sum) < 2;
}

Running it on the examples:

$ ./triplet-sum 1.2 0.4 0.1 2.5
1

$ ./triplet-sum 0.2 1.5 0.9 1.1
0

$ ./triplet-sum 0.5 1.1 0.3 0.7
1

We can make it even shorter, by inlining the procedure:

File: triplet-sum-inlined
unit sub MAIN (*@R where @R.elems > 0 && all(@R) > 0);

say + so 1 < any(@R.combinations(3)>>.sum) < 2;

A Perl Version

«combinations» is available in the «Algorithm::Combinatorics» module, and «sum» is available in the «List::Util» module. I have chosen to replace the «any» Junction (which is also available in the «List::Util» module) with a loop. (Note the all Junction used to verify the argument type.)

File: triplet-sum-perl
#! /usr/bin/env perl

use strict;
use feature 'say';

use Getopt::Long;
use List::Util qw(all sum);
use Algorithm::Combinatorics 'combinations';
    
my $verbose = 0;

GetOptions("verbose"  => \$verbose);

die "At least 3 values" unless @ARGV > 2;
die "Only positive Real numbers" unless all { $ ~= /^\d+(\.\d+)?$/ } @ARGV;
    
say ": ARGS: ", join(", ", @ARGV) if $verbose;

for my $combination (combinations(\@ARGV, 3))
{
  my $sum = sum(@$combination);
    
  say "Comb: ", join(", ", @$combination), " sum: $sum" if $verbose;

  if ($sum > 1 && $sum < 2)
  {
    say 1;
    exit;
  }
}

say 0;

Note that the loop replacing the «any» Junction short circuits, as it terminates the program (exit) when (if) it finds a triplet that satisfies the challenge.

Running it on the examples:

$  ./triplet-sum-perl -v 1.2 0.4 0.1 2.5
: ARGS: 1.2, 0.4, 0.1, 2.5
Comb: 1.2, 0.4, 0.1 sum: 1.7
1

$ ./triplet-sum-perl -v 0.2 1.5 0.9 1.1
: ARGS: 0.2, 1.5, 0.9, 1.1
Comb: 0.2, 1.5, 0.9 sum: 2.6
Comb: 0.2, 1.5, 1.1 sum: 2.8
Comb: 0.2, 0.9, 1.1 sum: 2.2
Comb: 1.5, 0.9, 1.1 sum: 3.5
0

$ ./triplet-sum-perl -v 0.5 1.1 0.3 0.7
: ARGS: 0.5, 1.1, 0.3, 0.7
Comb: 0.5, 1.1, 0.3 sum: 1.9
1

Challenge #085.2: Power of Two Integers

You are given a positive integer $N.

Write a script to find if it can be expressed as a ** b where a > 0 and b > 1. Print 1 if you succeed otherwise 0.

Example 1
Input: 8
Output: 1 as 8 = 2 ** 3
Example 2
Input: 15
Output: 0
Example 3
Input: 125
Output: 1 as 125 = 5 ** 3

Note that 5 ** 3 is the same as 5 * 5 * 5.

So the task is really to get the factors of the number $N, and check if any of the factors (which by the way are prime numbers) occur more than one time (which follows from «b > 1»). That is a task for repeated, which will remove the first instance of any distinct value from the list, keeping the rest (if any).

The «factors» procedure come from the program with the same name (in the section with the same name) in my Centenary Sequences with Raku - Part 5: Divisors and Factors article.

File: power-of-two-integers
unit sub MAIN (Int $N where $N > 0, :v(:$verbose));

my @factors  = factors($N);
my @repeated = @factors.repeated;

if $verbose
{
  say ": Factors: { @factors.join(", ") }";
  say ": Repeated: { @repeated.join(", ") }";
}

say @repeated ?? 1 !! 0;

sub factors ($number is copy)
{
  return (1)       if $number == 1;
  return ($number) if $number.is-prime;

  my @factors;

  for (2 .. $number div 2).grep( *.is-prime) -> $candidate
  {
    while $number %% $candidate
    {
      @factors.push: $candidate;
      $number /= $candidate;
    }
  }
    
  return @factors;
}

See docs.raku.org/routine/repeated for more information about the repeated method.

Running it:

$ ./power-of-two-integers -v 8
: Factors: 2, 2, 2
: Repeated: 2, 2
1

$ ./power-of-two-integers -v 15
: Factors: 3, 5
: Repeated: 
0

$ ./power-of-two-integers -v 125
: Factors: 5, 5, 5
: Repeated: 5, 5
1

A Perl Version

My Raku «factors» procedure is available as «factor» in the Math::Prime::Util» module, and the built-in «repeated» is available as «duplicates» in the «List::MoreUtils» module. The last one will only give one of each repeated value, and not 1 or more as we got from «repeated» But that does not matter here.

File: power-of-two-integers-perl
#! /usr/bin/env perl

use strict;
use feature 'say';

use Getopt::Long;
use Math::Prime::Util 'factor';
use List::MoreUtils 'duplicates';

my $verbose = 0;

GetOptions("verbose"  => \$verbose);

my $N = $ARGV[0] // die "Please specify a number";

my @factors  = factor($ARGV[0]);
my @repeated = duplicates(@factors);

if ($verbose)
{
  say ": Factors: ",  join(", ", @factors);
  say ": Repeated: ", join(", ", @repeated);
}

say @repeated ? 1 : 0;

Running it gives the same result as the Raku version:

$ ./power-of-two-integers-perl-errorfix --verbose 8
: Factors: 2, 2, 2
: Repeated: 2
1

$ ./power-of-two-integers-perl-errorfix --verbose 15
: Factors: 3, 5
: Repeated: 
0

$ ./power-of-two-integers-perl-errorfix --verbose 125
: Factors: 5, 5, 5
: Repeated: 5
1

List::MoreUtils Problems

The program above crashed for me, as Perl was unable to locate «duplicates»:

$ ./power-of-two-integers-perl 11
Could not find sub 'duplicates' exported by List::MoreUtils at
  ./power-of-two-integers-perl line 8.
BEGIN failed--compilation aborted at ./power-of-two-integers-perl line 8.

I have no idea why it failed. But a workaround is to remove the «use» line, and add the procedure to the program.

A version of the program («power-of-two-integers-perl-errorfix») doing just that, is included in the zip file.

And that's it.