RabbitFarm

2020-11-08

Command Line Arguments with SWI_Prolog’s library(optparse)

SWI-Prolog has a nice library which allows you to pass arguments on the command line to your Prolog programs. Here are a couple of example of it’s use done with the two parts of Perl Weekly Challenge 085 implemented in Prolog.

Part 1

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.

Solution


:- use_module(library(optparse)).
/*
    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.
*/
opts_spec(
    [
        [opt(numbers), 
        default([1.2, 0.4, 0.1, 2.5]),
        longflags([numbers])]
    ]).

ch_1(L):-
    member(A, L),
    member(B, L),
    member(C, L),
    A =\= B,
    B =\= C,
    A =\= C,
    D is A + B + C,
    D > 1,
    D < 2,
    writeln(1).
    
ch_1(_):-  
    writeln(0).
    
main:-
    opts_spec(OptsSpec),
    opt_arguments(OptsSpec, [numbers(N)], _AdditionalArguments),
    ch_1(N),
    halt.

Sample Run


$ swipl -s ch-1.p -g main --numbers="[1.0, 0.2, 3.4, 0.1]"
1
$ swipl -s ch-1.p -g main --numbers="[1.0, 1.2, 3.4, 0.1]"
0

Notes

You can see from the Sample Run that we are passing a Prolog list as a command line argument. To avoid conflicts with the shell trying to interpret our square brackets and commas we put the list itself in quotes. But how does Prolog know what --numbers means?

To use library(optparse) you must define a specification for each of your command line arguments that you expect to take. Here we just have one. The line opt(numbers) specifies the term that will be used to obtain the value in your program, default([1.2, 0.4, 0.1, 2.5]) provides a default value if the argument is not used on the command line. longflags([numbers]) indicates that to look for a flag of the form --numbers. we could have also used shortflags([numbers]) instead if we would prefer to use -numbers. I don’t know of any strong arguments for either one but find, personally, that the long form is more intuitive.

The values passed on the command line are extracted using opt_arguments which will follow the specification you’ve provided. _AdditionalArguments refers to any arguments passed on the command line without dashes but here we do not have any. [numbers(N)] is a list of the parsed key(value) pairs from the command line. So for this example N is the list value we entered on the command line and it is then used as needed.

Part 2

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.

Solution


:- use_module(library(clpfd)).
:- use_module(library(optparse)).
/*
    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.
*/
opts_spec(
    [
        [opt(number), 
        default(0),
        longflags([number])]
    ]).

/*
    Ok, I'll admit, this is a pretty silly use of clpfd when
    a simple logarithm calculation would do the job! Still clpfd 
    is more fun.
*/    
ch_2(N) :-
    N0 is N -1,
    A in 0 .. N0,    
    B in 1 .. N0,
    N #= A ^ B,
    label([A,B]),
    writeln(1).
ch_2(_) :-
    writeln(0).
    
main:-
    opts_spec(OptsSpec),
    opt_arguments(OptsSpec, [number(N)], _AdditionalArguments),
    ch_2(N),
    halt.

Sample Run


$ swipl -s ch-2.p -g main --number=7
0
$ swipl -s ch-2.p -g main --number=8
1

Notes

For an extra bit of fun I decided to use library(clpfd) for this although it is most definitely a bit of over engineering! This can be done rather simply using logarithms. Here we see the same pattern as done in Part 1: define the specification, extract the values from the command line, and then use the values. Here the single argument is just a single value and so there is no need to wrap the value in quotes, however, if you add quotes anyway it will have no effect. For example:

$ swipl -s ch-2.p -g main --number="100"
1

Reference

optparse documentation

posted at: 17:11 by: Adam Russell | path: /prolog | permanent link to this entry

Perl Weekly Challenge 085

Part 1

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.

Solution


use strict;
use warnings;
##
# 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.
##
use boolean;
use Math::Combinatorics;

sub build_constraints{
    my @constraints;
    my $a_not_equal_b = sub { $_[0] != $_[1] };
    my $a_not_equal_c = sub { $_[0] != $_[2] };
    my $b_not_equal_c = sub { $_[1] != $_[2] };
    my $sum_greater_than_1 = sub { 1 < ($_[0] + $_[1] + $_[2]) };
    my $sum_less_than_2 = sub { 2 > ($_[0] + $_[1] + $_[2]) };
    return (
       $a_not_equal_b,
       $a_not_equal_c,
       $b_not_equal_c,
       $sum_greater_than_1,
       $sum_less_than_2
    );
}

MAIN:{
    my $combinations = Math::Combinatorics->new(
                           count => 3,
                           data => [@ARGV],
                       );
    my $found;                  
    while(my @combination = $combinations->next_combination()){  
        $found = true;  
        for my $constraint (build_constraints()){
            if(!$constraint->(@combination)){
                $found = false;
                last;
            }
        }
        do{ print "1\n"; last; } if($found);
    }
    print "0\n" if(!$found);
}

Sample Run


$ perl perl/ch-1.pl 0.1 1.2 3.4 0.2
1
$ perl perl/ch-1.pl 1.1 1.2 3.4 0.2
0

Notes

I decided to try a constraint programming approach for this. While there are several modules for doing this available on CPAN I didn’t want to quite go so deep down that rabbithole. Instead I took the simpler path of implementing each constraint as a subroutine stored in an array. For each candidate combination the constraints are tests. Since all constraints must be satisfied if any one returns a false value then we can move immediately to the next combination.

Part 2

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.

Solution


use strict;
use warnings;
##
# 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.
##
use boolean;

sub log_a{
    my($a, $n) = @_;
    return log($n)/log($a);
}

MAIN:{
    my $N = $ARGV[0];
    my $found = false;                  
    for my $a (2 .. $N){ 
        my $b = log_a($a, $N);
        if($b =~ /^[-]?\d+$/ && $b > 1){ 
            print "1\n";
            $found = true;
            last;
        }
    }
    print "0\n" if(!$found);
}

Sample Run


$ perl perl/ch-2.pl 7
0
$ perl perl/ch-2.pl 9
1

Notes

I was tempted to repeat roughly the same design as Part 1 and use constraints but that really would be over engineering it! Instead here we just loop over all possible values $a and test using logarithms to see if $b holds an integer value. There seems to be a number of ways to do the test to determine if a scalar holds an integer but a regex seems maybe the most idiomatically Perlish way.

posted at: 16:20 by: Adam Russell | path: /perl | permanent link to this entry