5.2 Loop/Iterator Constructs
Many types of simple iterations are inconvenient to write in the
form of recursive predicates. ECLiPSe therefore provides a logical
iteration construct
do/2,
which can be understood either by itself
or by its translation to an equivalent recursion.
A simple example is the traversal of a list
main :-
write_list([1,2,3]).
write_list([]).
write_list([X|Xs]) :-
writeln(X),
write_list(Xs).
which can be written as follows without the need for an auxiliary predicate:
main :-
( foreach(X, [1,2,3]) do
writeln(X)
).
This looks very much like a loop in a procedural language. However,
due to the relational nature of logic programming, the same foreach-
construct can be used not only to control iteration over an existing list,
but also to build a new list during an iteration. For example
main :-
( foreach(X, [1,2,3]), foreach(Y, Negatives) do
Y is -X
),
writeln(Negatives).
will print [-1, -2, -3].
The general form of a do-loop is
( IterationSpecs do Goals )
and it corresponds to a call to an auxiliary recursive
predicate of the form
do__n(...).
do__n(...) :- Goals, do__n(...).
The IterationSpecs determine the number of times the loop is executed
(i.e. the termination condition), and the way information is passed
into the loop, from one iteration to the next, and out of the loop.
IterationSpecs is one (or a comma-separated sequence) of the following:
-
fromto(First,In,Out,Last)
iterate Goals starting with In=First until Out=Last.
In and Out are local variables in Goals. For all but the first
iteration, the value of In is the same as the value of Out in the
previous iteration.
- foreach(X,List)
iterate Goals with X ranging over all elements of List.
X is a local variable in Goals.
Can also be used for constructing a list.
- foreacharg(X,Struct)
iterate Goals with X ranging over all elements of Struct.
X is a local variable in Goals.
Cannot be used for constructing a term.
- foreacharg(X,Struct,Idx)
same as before, but Idx is set to the argument position of X in Struct,
i.e. arg(Idx, Struct, X)
is true.
Idx is a local variable in Goals.
- foreachelem(X,Array)
like foreacharg/2, but iterates over all elements of an array
of arbitrary dimension. The order is the natural order, i.e.
if Array = []([](a, b, c), [](d, e, f))
, then for successive
iterations X is bound in turn to a, b, c, d, e and f.
X is a local variable in Goals.
Cannot be used for constructing a term.
- foreachelem(X,Array,Idx)
same as before, but Idx is set to the index position of X in
Array, i.e. subscript(Array, Idx, X)
is true.
Idx is a local variable in Goals.
- foreachindex(Idx,Array)
like foreachelem/3, but returns just the index position and not the
element.
- for(I,MinExpr,MaxExpr)
iterate Goals with I ranging over integers from MinExpr to MaxExpr.
I is a local variable in Goals.
MinExpr and MaxExpr can be arithmetic expressions.
Can be used only for controlling iteration, i.e. MaxExpr cannot
be uninstantiated.
- for(I,MinExpr,MaxExpr,Increment)
same as before, but Increment can be specified (it defaults to 1).
- multifor(List,MinList,MaxList)
like for/3, but allows iteration over multiple indices (saves
writing nested loops). Each element of List takes a value
between the corresponding elements in MinList and MaxList.
Successive iterations go through the possible combinations of
values for List in lexicographic order. List is a local
variable in Goals. MinList and MaxList must be either lists of
arithmetic expressions evaluating to integers, or arithmetic
expressions evaluating to integers (in the latter case they are
treated as lists containing the (evaluated) integer repeated an
appropriate number of times). At least one of List, MinList and
MaxList must be a list of fixed length at call time so that it is
known how many indices are to be iterated.
- multifor(List,MinList,MaxList,IncrementList)
same as before, but IncrementList can be specified (i.e. how
much to increment each element of List by). IncrementList must be
either a list of arithmetic expressions evaluating to non-zero
integers, or an arithmetic expression evaluating to a non-zero
integer (in which case all elements are incremented by this
amount). IncrementList defaults to 1.
- count(I,Min,Max)
iterate Goals with I ranging over integers from Min up to Max.
I is a local variable in Goals.
Can be used for controlling iteration as well as counting,
i.e. Max can be a variable.
- param(Var1,Var2,...)
for declaring variables in Goals global, ie shared with the context.
CAUTION: By default, variables in Goals are local!
Note that fromto/4 is the most general specifier (subsuming the
functionality of all the others), but foreach/2, foreacharg/2,3,
foreachelem/2,3, foreachindex/2, count/3, for/3,4, multifor/3,4 and
param/N are convenient shorthands.
There are three ways to combine the above specifiers in a single do loop:
-
IterSpec1, IterSpec2
- (“synchronous iteration”)
This is the normal way to combine iteration specifiers: simply
provide a comma-separated sequence of them. The specifiers are
iterated synchronously; that is, they all take their first
“value” for the first execution of Goals, their second “value”
for the second execution of Goals, etc. The order in which they
are written does not matter, and the set of local variables in
Goals is the union of those of IterSpec1 and IterSpec2.
When multiple iteration specifiers are given in this way,
typically not all of them will impose a termination condition on
the loop (e.g. foreach with an uninstantiated list and count with an uninstantiated maximum do not impose a termination
condition), but at least one of them should do so. If several
specifiers impose termination conditions, then these conditions
must coincide, i.e. specify the same number of iterations.
- IterSpec1 * IterSpec2
- (“cross product”)
This iterates over the cross product of IterSpec1 and IterSpec2.
The sequence of iteration is to iterate IterSpec2 completely for a
given “value” of IterSpec1 before doing the same with the next
“value” of IterSpec1, and so on. The set of local variables in
Goals is the union of those of IterSpec1 and IterSpec2.
- IterSpec1 >> IterSpec2
- (“nested iteration”)
Like ( IterSpec1 do ( IterSpec2 do Goals ) ), including with
respect to scoping. The local variables in Goals are those of
IterSpec2; in particular, those of IterSpec1 are not available
unless IterSpec2 passes them through, e.g. using a param.
Similarly, the only “external” variables available as inputs to
IterSpec2 are the locals of IterSpec1; variables from outside the
loop are not available unless passed through by IterSpec1, e.g.
using a param.
Syntactically, the do-operator binds like the semicolon, i.e. less than comma.
That means that the whole do-construct should always be enclosed in
parentheses (see examples).
Unless you use :-pragma(noexpand) or :-dbgcomp, the do-construct is
compiled into an efficient auxiliary predicate named do__nnn, where
nnn is a unique integer. This will be visible during debugging.
To make debugging easier, it is possible to give the loop a
user-defined name by adding loop_name(Name)
to the iteration specifiers. Name must be an atom, and is used as the
name of the auxiliary predicate into which the loop is compiled
(instead of do__nnn). The name should therefore not clash with other
predicate names in the same module.
Iterate over list
foreach(X,[1,2,3]) do writeln(X).
Maplist (construct a new list from an existing list)
(foreach(X,[1,2,3]), foreach(Y,List) do Y is X+3).
Sumlist
(foreach(X,[1,2,3]), fromto(0,In,Out,Sum) do Out is In+X).
Reverse list
(foreach(X,[1,2,3]), fromto([],In,Out, Rev) do Out=[X|In]). % or:
(foreach(X,[1,2,3]), fromto([],In,[X|In],Rev) do true).
Iterate over integers from 1 up to 5
for(I,1,5) do writeln(I). % or:
count(I,1,5) do writeln(I).
Iterate over integers from 5 down to 1
(for(I,5,1,-1) do writeln(I)).
Make list of integers [1,2,3,4,5]
(for(I,1,5), foreach(I,List) do true). % or:
(count(I,1,5), foreach(I,List) do true).
Make a list of length 3
(foreach(_,List), for(_,1,3) do true). % or:
(foreach(_,List), count(_,1,3) do true).
Get the length of a list
(foreach(_,[a,b,c]), count(_,1,N) do true).
Actually, the length/2 builtin is (almost)
length(List, N) :- (foreach(_,List), count(_,1,N) do true).
Iterate [I,J] over [1,1], [1,2], [1,3], [2,1], ..., [3,3]:
(multifor([I,J],1,3) do writeln([I,J])).
Similar, but have different start/stop values for I and J:
(multifor([I,J], [2,1], [4,5]) do writeln([I,J])).
Similar, but only do odd values for the second variable:
(multifor(List, [2,1], [4,5], [1,2]) do writeln(List)).
Filter list elements
(foreach(X,[5,3,8,1,4,6]), fromto(List,Out,In,[]) do
X>3 -> Out=[X|In] ; Out=In).
Iterate over structure arguments
(foreacharg(X,s(a,b,c,d,e)) do writeln(X)).
Collect args in list
(bad example, use =.. if you really want to do that!)
(foreacharg(X,s(a,b,c,d,e)), foreach(X,List) do true).
Collect args reverse
(foreacharg(X,s(a,b,c,d,e)), fromto([],In,[X|In],List) do true).
or like this:
S = s(a,b,c,d,e), functor(S, _, N),
(for(I,N,1,-1), foreach(A,List), param(S) do arg(I,S,A)).
Rotate args in a struct
S0 = s(a,b,c,d,e), functor(S0, F, N), functor(S1, F, N),
(foreacharg(X,S0,I), param(S1, N) do I1 is (I mod N)+1, arg(I1,S1,X)).
Flatten an array into a list
(foreachelem(X,[]([](5,1,2),[](3,3,2))), foreach(X,List) do true).
Transpose a 2D array
A = []([](5,1,2),[](3,3,2)), dim(A, [R,C]), dim(T, [C,R]),
(foreachelem(X,A,[I,J]), param(T) do X is T[J,I]).
Same, using foreachindex
A = []([](5,1,2),[](3,3,2)), dim(A, [R,C]), dim(T, [C,R]),
(foreachindex([I,J],A), param(A, T) do
subscript(A, [I,J], X), subscript(T, [J,I], X)).
The following two are equivalent
foreach(X,[1,2,3]) do writeln(X).
fromto([1,2,3],In,Out,[]) do In=[X|Out], writeln(X).
The following two are equivalent
count(I,1,5) do writeln(I).
fromto(0,I0,I,5) do I is I0+1, writeln(I).
Some examples for nested loops. Print all pairs of list elements:
Xs = [1,2,3,4],
( foreach(X, Xs), param(Xs) do
( foreach(Y,Xs), param(X) do
writeln(X-Y)
)
).
% or
Xs = [1,2,3,4],
( foreach(X, Xs) * foreach(Y, Xs) do
writeln(X-Y)
).
and the same without symmetries:
Xs = [1,2,3,4],
( fromto(Xs, [X|Xs1], Xs1, []) do
( foreach(Y,Xs1), param(X) do
writeln(X-Y)
)
).
% or
Xs = [1,2,3,4],
( fromto(Xs, [X|Xs1], Xs1, []) >> ( foreach(Y,Xs1), param(X) ) do
writeln(X-Y)
).
Find all pairs of list elements and collect them in a result list:
pairs(Xs, Ys, Zs) :-
(
foreach(X,Xs),
fromto(Zs, Zs4, Zs1, []),
param(Ys)
do
(
foreach(Y,Ys),
fromto(Zs4, Zs3, Zs2, Zs1),
param(X)
do
Zs3 = [X-Y|Zs2]
)
).
% or
pairs(Xs, Ys, Zs) :-
(
foreach(X, Xs) * foreach(Y, Ys),
foreach(Z, Zs)
do
Z = X-Y
).
Flatten a 2-dimensional matrix into a list:
flatten_matrix(Mat, Xs) :-
dim(Mat, [M,N]),
(
for(I,1,M),
fromto(Xs, Xs4, Xs1, []),
param(Mat,N)
do
(
for(J,1,N),
fromto(Xs4, [X|Xs2], Xs2, Xs1),
param(Mat,I)
do
subscript(Mat, [I,J], X)
)
).
Same using * to avoid nesting:
flatten_matrix(Mat, Xs) :-
dim(Mat, [M,N]),
(
for(I, 1, M) * for(J, 1, N),
foreach(X, Xs),
param(Mat)
do
subscript(Mat, [I,J], X)
).
Same using multifor to avoid nesting:
flatten_matrix(Mat, Xs) :-
dim(Mat, [M,N]),
(
multifor([I,J], 1, [M,N]),
foreach(X, Xs),
param(Mat)
do
subscript(Mat, [I,J], X)
).
Same for an array of arbitrary dimension:
flatten_array(Array, Xs) :-
dim(Array, Dims),
(
multifor(Idx, 1, Dims),
foreach(X, Xs),
param(Array)
do
subscript(Array, Idx, X)
).
Same but returns the elements in the reverse order:
flatten_array(Array, Xs) :-
dim(Array, Dims),
(
multifor(Idx, Dims, 1, -1),
foreach(X, Xs),
param(Array)
do
subscript(Array, Idx, X)
).
Flatten nested lists one level (cf. flatten/2 which flattens completely):
List = [[a,b],[[c,d,e],[f]],[g]],
(foreach(Xs,List) >> foreach(X,Xs), foreach(X,Ys) do true).
Iterate over all ordered pairs of integers 1..4 (param(I) required to make
I available in body of loop):
(for(I,1,4) >> (for(J,I+1,4), param(I)) do writeln(I-J)).
Same for general 1..N (param(N) required to make N available to second for):
N=4,
((for(I,1,N), param(N)) >> (for(J,I+1,N), param(I)) do writeln(I-J)).