Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to declare an ascending list?

Tags:

list

prolog

clpfd

I can make lists of ascending integer like so:

?- findall(L,between(1,5,L),List).

I know I can also generate values using:

?- length(_,X).

But I don't think I can use this in a findall, as things like the following loop:

?- findall(X,(length(_,X),X<6),Xs).

I can also generate a list using clpfd.

:- use_module(library(clpfd)).

list_to_n(N,List) :-
   length(List,N),
   List ins 1..N,
   all_different(List),
   once(label(List)).

or

list_to_n2(N,List) :-
   length(List,N),
   List ins 1..N,
   chain(List,#<),
   label(List).

The last method seems best to me as it is the most declarative, and does not use once/1 or between/3 or findall/3 etc.

Are there other ways to do this? Is there a declarative way to do this in 'pure' Prolog? Is there a 'best' way?

like image 748
user27815 Avatar asked Sep 16 '15 14:09

user27815


People also ask

How do you make an ascending list in Python?

Python List sort() - Sorts Ascending or Descending List. The list. sort() method sorts the elements of a list in ascending or descending order using the default < comparisons operator between items. Use the key parameter to pass the function name to be used for comparison instead of the default < operator.

How do you list in ascending order?

Ascending order means the smallest or first or earliest in the order will appear at the top of the list: For numbers or amounts, the sort is smallest to largest. Lower numbers or amounts will be at the top of the list. For letters/words, the sort is alphabetical from A to Z.

How do you put numbers in ascending order in Python?

sorted() , with no additional arguments or parameters, is ordering the values in numbers in an ascending order, meaning smallest to largest. The original numbers variable is unchanged because sorted() provides sorted output and does not change the original value in place.

Can we sort a list in Python?

The sort() method is one of the ways you can sort a list in Python. When using sort() , you sort a list in-place. This means that the original list is directly modified. Specifially, the original order of elements is altered.


1 Answers

The "best" way depends on your concrete use cases! Here's another way to do it using clpfd:

:- use_module(library(clpfd)).

We define predicate equidistant_stride/2 as suggested by @mat in a comment to a previous answer of a related question:

equidistant_stride([],_).
equidistant_stride([Z|Zs],D) :- 
   foldl(equidistant_stride_(D),Zs,Z,_).

equidistant_stride_(D,Z1,Z0,Z1) :-
   Z1 #= Z0+D.

Based on equidistant_stride/2, we define:

consecutive_ascending_integers(Zs) :-
   equidistant_stride(Zs,1).

consecutive_ascending_integers_from(Zs,Z0) :-
   Zs = [Z0|_],
   consecutive_ascending_integers(Zs).

consecutive_ascending_integers_from_1(Zs) :-
   consecutive_ascending_integers_from(Zs,1).

Let's run some queries! First, your original use case:

?- length(Zs,N), consecutive_ascending_integers_from_1(Zs).
  N = 1, Zs = [1]
; N = 2, Zs = [1,2]
; N = 3, Zs = [1,2,3]
; N = 4, Zs = [1,2,3,4]
; N = 5, Zs = [1,2,3,4,5]
...

With clpfd, we can ask quite general queries—and get logically sound answers, too!

?- consecutive_ascending_integers([A,B,0,D,E]).
A = -2, B = -1, D = 1, E = 2.

?- consecutive_ascending_integers([A,B,C,D,E]).
A+1#=B, B+1#=C, C+1#=D, D+1#=E.

An alternative implementation of equidistant_stride/2:

I hope the new code makes better use of constraint propagation.

Thanks to @WillNess for suggesting the test-cases that motivated this rewrite!

equidistant_from_nth_stride([],_,_,_).
equidistant_from_nth_stride([Z|Zs],Z0,N,D) :-
   Z  #= Z0 + N*D,
   N1 #= N+1,
   equidistant_from_nth_stride(Zs,Z0,N1,D).

equidistant_stride([],_).
equidistant_stride([Z0|Zs],D) :-
   equidistant_from_nth_stride(Zs,Z0,1,D).

Comparison of old vs new version with @mat's clpfd:

First up, the old version:

?- equidistant_stride([1,_,_,_,14],D).
_G1133+D#=14,
_G1145+D#=_G1133,
_G1157+D#=_G1145,
1+D#=_G1157.                               % succeeds with Scheinlösung

?- equidistant_stride([1,_,_,_,14|_],D).
  _G1136+D#=14, _G1148+D#=_G1136, _G1160+D#=_G1148, 1+D#=_G1160
; 14+D#=_G1340, _G1354+D#=14, _G1366+D#=_G1354, _G1378+D#=_G1366, 1+D#=_G1378
...                                        % does not terminate universally

Now let's switch to the new version and ask the same queries!

?- equidistant_stride([1,_,_,_,14],D).      
false.                                     % fails, as it should

?- equidistant_stride([1,_,_,_,14|_],D).
false.                                     % fails, as it should

More, now, again! Can we fail earlier by tentatively employing redundant constraints?

Previously, we proposed using constraints Z1 #= Z0+D*1, Z2 #= Z0+D*2, Z3 #= Z0+D*3 instead of Z1 #= Z0+D, Z2 #= Z1+D, Z3 #= Z2+D (which the 1st version of code in this answer did).

Again, thanks to @WillNess for motivating this little experiment by noting that the goal equidistant_stride([_,4,_,_,14],D) does not fail but instead succeeds with pending goals:

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D).
Zs = [_G2650, 4, _G2656, _G2659, 14],
14#=_G2650+4*D,
_G2659#=_G2650+3*D,
_G2656#=_G2650+2*D,
_G2650+D#=4.

Let's add some redundant constraints with equidistantRED_stride/2:

equidistantRED_stride([],_).
equidistantRED_stride([Z|Zs],D) :-
   equidistant_from_nth_stride(Zs,Z,1,D),
   equidistantRED_stride(Zs,D).

Sample query:

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D), equidistantRED_stride(Zs,D).
false.

Done? Not yet! In general we don't want a quadratic number of redundant constraints. Here's why:

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D).
Zs = [_G2683, _G2686, _G2689, _G2692, 14],
14#=_G2683+4*D,
_G2692#=_G2683+3*D,
_G2689#=_G2683+2*D,
_G2686#=_G2683+D.

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D), equidistantRED_stride(Zs,D).
Zs = [_G831, _G834, _G837, _G840, 14],
14#=_G831+4*D,
_G840#=_G831+3*D,
_G837#=_G831+2*D,
_G834#=_G831+D,
14#=_G831+4*D,
_G840#=_G831+3*D,
_G837#=_G831+2*D,
_G834#=_G831+D,
D+_G840#=14,
14#=2*D+_G837,
_G840#=D+_G837,
14#=_G834+3*D,
_G840#=_G834+2*D,
_G837#=_G834+D.

But if we use the double-negation trick, the residuum remains in cases that succeed ...

?- Zs = [_,_,_,_,14], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
Zs = [_G454, _G457, _G460, _G463, 14],
14#=_G454+4*D,
_G463#=_G454+3*D,
_G460#=_G454+2*D,
_G457#=_G454+D.

... and ...

?- Zs = [_,4,_,_,14], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
false.

... we detect failure in more cases than we did before!


Let's dig a little deeper! Can we detect failure early in even more generalized uses?

With code presented so far, these two logically false queries do not terminate:

?- Zs = [_,4,_,_,14|_], \+ \+ equidistantRED_stride(Zs,D), equidistant_stride(Zs,D).
...                                        % Execution Aborted

?- Zs = [_,4,_,_,14|_], equidistant_stride(Zs,D), \+ \+ equidistantRED_stride(Zs,D).
...                                        % Execution Aborted

Got fix? Got hack!

?- use_module(library(lambda)).
true.

?- Zs = [_,4,_,_,14|_], 
   \+ ( term_variables(Zs,Vs), 
        maplist(\X^when(nonvar(X),integer(X)),Vs),
        \+ equidistantRED_stride(Zs,D)),
   equidistant_stride(Zs,D).
false.

The hack doesn't guarantee termination of the redundant constraint "part", but IMO it's not too bad for a quick first shot. The test integer/1 upon instantiation of any variable in Zs is meant to allow the clpfd solver to constrain variable domains to singletons, while the instantiation with cons-pairs (which directly leads to non-termination of list-based predicates) is suppressed.

I do realize that the hack can be broken quite easily in more than one way (e.g., using cyclic terms). Any suggestions and comments are welcome!

like image 181
repeat Avatar answered Oct 07 '22 09:10

repeat