I'm trying to write Prolog code that can swap two elements of a list, but only if they are consecutive to each other. That is,
conseq_swap(d, e, [a, g, d, e, f], X).
should give:
X = [a, g, e, d, f].
(d and e are consecutive.)
However,
conseq_swap(a, e, [a, g, d, e, f], X).
should always fail (a and e are not consecutive.)
I can assume that an item appears in the list only once.
I have the following code, which is actually working fine:
swap_conseq(X, Y, MainList, SwappedList) :-
indexOf(MainList, X, Xpos),
indexOf(MainList, Y, Ypos),
Diff is Ypos - Xpos,
Diff is 1,
Xpos < Ypos,
swap_ordered(X, Y, Xpos, Ypos, MainList, SwappedList).
swap_conseq(X, Y, MainList, SwappedList) :-
indexOf(MainList, X, Xpos),
indexOf(MainList, Y, Ypos),
Diff is Xpos - Ypos,
Diff is 1,
Ypos < Xpos,
swap_ordered(Y, X, Ypos, Xpos, MainList, SwappedList).
swap_ordered(Min, Max, Minpos, Maxpos, MainList, SwappedList) :-
compute_lists(MainList, Min, Minpos, Pre, _),
compute_lists(MainList, Max, Maxpos, _, Post),
append(Pre, [Max, Min], Temp),
append(Temp, Post, SwappedList).
indexOf([Element|_], Element, 1):- !.
indexOf([_|Tail], Element, Index):-
indexOf(Tail, Element, Index1),
!,
Index is Index1+1.
compute_lists(MainList, X, Xpos, A, B) :-
L is Xpos - 1,
append(A, [X | B], MainList),
length(A, L).
However, just by looking at the code, I can tell that this is a horrible way to do this - repetitive, inefficient - something only a Prolog newbie like me could write.
Any suggestions on how to improve this would be greatly appreciated!
Here's a logically pure implementation of
conseq_swap/4
:Compared to
conseq_swap/4
the argument order oflist_item1_item2_swapped/4
is altered, so first argument indexing is enabled. This can help prevent unneeded choice-points.We thread an extra argument that refers to the previous list element through, employing a technique commonly called "lagging". To see another use of this technique in action, look at @mat's answer to some other question about implementing predicates on Prolog lists.
The actual relation is defined by
list_prev_item1_item2_swapped/5
:Done! Now let's run some queries with SWI-Prolog 7.1.37:
Edit 2015-04-24
Here's a more direct, somewhat de-optimized variant of the code given before.
It is less efficient, but hopefully a bit easier to read by humans. As pure as the other variant.
Same queries, same answers:
Another solution (in SWI-Prolog) would be:
Same queries, same answers:
(Sorry if I'm not able to write so much like "repeat", but I'm italian).
(Do read answers by repeat and Ludwig. Those are good answers)
Modified to remove both assumptions in old solution
Cuts (
(!)/0
) are removed.For the case where there are many possible pairs to swap, it output all ways of swapping only once. The question assumes that there can only be one pair anyway.
Modified to remove assumption 2
If you want the query
conseq_swap(a, e, [a, g, d, e, f], X).
to fail outright, remove the first two lines in the old solution, which allows the original list to end up as output when no swapping is performed.Old solution (same output as the code in question)
This is the old solution written with the following assumptions: