19

Consider what I have tried:

dif_to_orto(A, B, C) :-
   (  dif(A, B)
   ;  dif(A, C)
   ).

While this definition is fine from a declarative viewpoint it contains many redundancies. Think of:

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 2.
   A = 1, B = 2, C = 2
;  A = 1, B = 2, C = 2.   % unexpected redundant solution

And not even in this case:

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 3.
   A = 1, B = 2, C = 3
;  A = 1, B = 2, C = 3.   % unexpected redundant solution

At least, here is a case without redundancy...

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 1.
   A = 1, B = 2, C = 1
;  false.                 % unexpected inefficient leftover choicepoint

...but with a resource-wasting leftover choicepoint.

Rare are the occasions where this definition is efficient:

?- dif_to_orto(A, B, C), A = 1, B = 1, C = 2.
   A = 1, B = 1, C = 2.

Also that the most general query produces two answers sounds very inefficient to me:

?- dif_to_orto(A, B, C).
   dif:dif(A,B)
;  dif:dif(A,C).

... which produces also the following redundancy:

?- dif_to_orto(1, B, B).
   dif:dif(1,B)
;  dif:dif(1,B).    % unexpected redundant answer

One dif/2 would be enough!

Is there a way to avoid all these redundancies and inefficiencies?

false
  • 10,264
  • 13
  • 101
  • 209

4 Answers4

16

How about this one:

dif_to_orto(A, B, C) :-
   dif(A-A, B-C).

The test cases:

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 2.
A = 1,
B = C, C = 2.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 3.
A = 1,
B = 2,
C = 3.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 1.
A = C, C = 1,
B = 2.

?- dif_to_orto(A, B, C), A = 1, B = 1, C = 2.
A = B, B = 1,
C = 2.

?- dif_to_orto(A, B, C).
dif(f(B, A), f(A, C)).

?- dif_to_orto(1, B, B).
dif(B, 1).
DuDa
  • 3,718
  • 4
  • 16
  • 36
  • @false I'm puzzled. Did I do anything wrong? Should I retreat this answer? – DuDa Feb 05 '21 at 21:34
  • No, looks like a perfect answer – false Feb 05 '21 at 21:35
  • `dif_to_orto(1, B, B).` is another nice case – false Feb 05 '21 at 21:36
  • In SICStus, the two last calls give different results: `prolog:dif(A-A,B-C)` and `prolog:dif(1-1,B-B)` respectively. So the `dif/2` goal is not "simplified". I think the behavior is ultimately the same, but I thought it was worth noting it... – jnmonette Feb 08 '21 at 07:33
  • @jnmonette: SICStus shows the original goal which is at least in the first case much more intuitive than what you get in SWI with its out-of-nowhere structure `f/2`. In the second case, SWI is in fact nicer. – false Feb 08 '21 at 22:33
8

This solution first awaits for 2 of the 3 variables to be comparable, then if it cannot decide whether the constraint should succeed adds a new constraint:

dif_to_orto(A, B, C) :-
    when((?=(A, B) ; ?=(A, C) ; ?=(B, C)),
         (   ?=(A, B) ->
              ( A\==B ->  true ; dif(A, C) )
         ;
             (
                ?=(A, C) ->
                 ( A\==C -> true ; dif(A, B) )
             ;
                 ( B\==C -> true ; dif(A, B) )
             )
         )).

Sample runs:

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 2.
A = 1,
B = C, C = 2.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 3.
A = 1,
B = 2,
C = 3.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 1.
A = C, C = 1,
B = 2.

?- dif_to_orto(A, B, C).
when((?=(A, C);?=(B, C);?=(A, B)),  (?=(A, B)->(A\==B->true;dif(A, C));?=(A, C)->(A\==C->true;dif(A, B));B\==C->true;dif(A, B))).

?- dif_to_orto(1, 2, Z).
true.

?- dif_to_orto(1, B, B).
dif(B, 1).

Reversing the checks:

dif_to_orto(A, B, C) :-
    when((?=(A, B) ; ?=(A, C) ; ?=(B, C)),
         (
           A==B -> dif(A, C)
           ;
           ((A==C ; B==C) -> dif(A, B) ; true)
         )).
gusbro
  • 22,357
  • 35
  • 46
6

Here is one suggestion. As far as I can tell, it does not create choice points or redundant solutions:

dif_to_orto(A, B, C) :-
   when(?=(A,B),(A==B->dif(A,C);true)),
   when(?=(A,C),(A==C->dif(A,B);true)).

For each disjunct, wait until it is known to be true or false. Once known, check its truth and, if false, then post the other disjunct.

jnmonette
  • 1,794
  • 4
  • 7
  • `dif_to_orto(1, 2, Z).` gives `when(?=(1, Z), (1==Z->dif(1, 2);true)).` How can I be sure that this is not tantamount to `false`? – false Feb 05 '21 at 18:20
  • BTW, if you have another solution, put it into another answer, so we can vote, accept and bounty them one-by-one. – false Feb 05 '21 at 18:23
5

Expanding on the definition of dif/2:

dif_to_orto(A, B, C):-
   when((?=(A,B), ?=(A, C)), (A \== B -> true ; A \== C)).

Sample runs:

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 2.
A = 1,
B = C, C = 2.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 3.
A = 1,
B = 2,
C = 3.

?- dif_to_orto(A, B, C), A = 1, B = 2, C = 1.
A = C, C = 1,
B = 2.

?- dif_to_orto(A, B, C).
when((?=(A, B), ?=(A, C)),  (A\==B->true;A\==C)).
gusbro
  • 22,357
  • 35
  • 46
  • `dif_to_orto(1, 2, Z).` has some constraint attached to `Z`. Why? How can I be sure that it is irrelevant. – false Feb 05 '21 at 18:09
  • This answer delays the evaluation of the constraint until all three variables can be syntactically compared so in your example I only see that a manual inspection of the suspended goal determines it will succeed. Maybe delaying the evaluation until the three of them are comparable is not the best idea then... – gusbro Feb 05 '21 at 18:28
  • Try another one. – false Feb 05 '21 at 18:33
  • BTW, syntax is here SWI-specific – false Feb 05 '21 at 19:02
  • @false: do you mean using `when/2` and `dif/2` ? I don't see them in GProlog. I don't have a license for Sicstus so I am not really familiar with their counterparts. – gusbro Feb 05 '21 at 19:06
  • Just a missing pair of round brackets around the if-then-else. – false Feb 05 '21 at 19:07
  • Or, (in SWI), say `listing(dif_to_orto)` – false Feb 05 '21 at 19:09
  • 1
    I see it from the last sample run, the if-then-else prints surrounded with round brackets. Fixed code – gusbro Feb 05 '21 at 19:10