gprolog - Simple way to determine whether one list is a permutation of another

…衆ロ難τιáo~ 提交于 2019-12-24 05:33:37

问题


I'm trying to write a prolog program that determines whether one list is a permutation of another. Input is of the form perm(L,M), which will be true if and only if list L is a permutation of list M.

This is for my AI class, so I cannot just use the nifty little permutation predicate that gprolog already provides. Our professor noted that the member predicate might be useful, but any ideas I have that involve it seem to require very tricky and not-so-declarative things (and I'm assuming there is a way to solve this without getting too advanced, since the class is new to prolog.)

Anyway, one way to check would supposedly be to see that L and M are the same size, each L element is in M, and each M element is in L (there's a use of member!). However, this wouldn't be enough for cases like [2,2,4] and [4,4,2], among others.

Another way could be to ensure that the same counts of each element are in the opposite list, but my impression of prolog is that any kind of variable 'memory' is rather difficult business (in fact, it seems that the example programs I see that perform sorts, etc., aren't really manipulating data at all; they're just 'hypothetically' rearranging things and then telling you yes or no...?)

Mentally, one could just sort both lists and check elements side-by-side, but that, among tons of other ways to think of it, seems a little too object-oriented...

Any hints? My biggest trouble seems to be (as mentioned) the fact that doing "operations" seems to be more like asking about them and hoping that things stay true long enough to get where you want.

**UPDATE: gprolog does offer a delete functionality, but it comes with the declarative-related trouble I was expecting, given an attempt like this:

perm([LH|LT], R) :- member(LH,R), delete([LH|LT],LH,R), perm(LT,R).

In the manual, delete is defined like this: "delete(List1, Element, List2) removes all occurrences of Element in List1 to provide List2. A strict term equality is required, cf. (==)/2"

Execution:

{trace}
| ?- perm([1,2,3],[3,1,2]).
      1    1  Call: perm([1,2,3],[3,1,2]) ? 
      2    2  Call: member(1,[3,1,2]) ? 
      2    2  Exit: member(1,[3,1,2]) ? 
      3    2  Call: delete([1,2,3],1,[3,1,2]) ? 
      3    2  Fail: delete([1,2,3],1,[3,1,2]) ? 
      2    2  Redo: member(1,[3,1,2]) ? 
      2    2  Fail: member(1,[3,1,2]) ? 
      1    1  Fail: perm([1,2,3],[3,1,2]) ? 

(1 ms) no

**UPDATE 2: I think I might have figured it out! It's kind of verbose, but I have tested it for quite a few cases and haven't found a bad one yet. If someone sees a major issue, please point it out:

perm([],[]). 
perm([LH|LT],R) :- length([LH|LT],A), length(R,B), A == B, member(LH,R), select(LH,[LH|LT],X), select(LH,R,Y), perm_recurse(X, Y), !.
perm_recurse([],X).  %If we get here, all elements successfully matched
perm_recurse([LH|LT],R) :- member(LH,R), select(LH,[LH|LT],X), select(LH,R,Y), perm_recurse(X, Y), !.  

I do like the cut operator..


回答1:


Always good to define more general predicate and use it in a narrowed fashion:

perm(X,L):- mselect(X,L,[]).

mselect([A|B],L,R):- select(A,L,M), mselect(B,M,R).
mselect([],L,L).

member is no good as it leaves the second list unchanged. delete is no good either as it deletes the multiplicities.

You could use append though. :) It too combines picking and removing:

perm([A|B],L):- length(L,N), between(0,N,I),length(X,I),
  append(X,[A],Y), append(Y,Z,L),
  append(X,Z,M), perm(B,M).
perm([],[]).



回答2:


perm(L, M) :- sort(L, X), sort(M, X).

This gets you pretty close and is fully declarative ("two lists are permutations of each other if they have the same sorted representation", but sorting in Prolog removes duplicates). However, it will succeed for cases like perm([1,2], [2,2,2,1]) which I'm not sure if you want. It will handle [2,2,4] and [4,4,2] though, since they both sort to [2,4]. Another solution would be something like this:

perm([], []).
perm([L|Ls], M) :- select(L, M, Ms), !, perm(Ls, Ms).

This version won't succeed for [2,2,4] and [4,4,2], but it will properly fail for [1,2] and [2,2,2,1]. I'm not sure which one you want, but I think one or the other of these is probably correct.




回答3:


The usual model to follow is inductive.

If you know how to build all permutation of N-1 elements, then all permutations of N elements are obtained inserting the element in all available positions.

A 'trick of the trade' is using the select/3 builtin, that, like member, 'peek' an element, but removes it from the list and 'returns' the smaller list. Those verbs are not really appropriate for Prolog. Let's say that select/3 is a relation among an element, a list containing it, and an identical list where it's missing.

Then let Prolog do all the search... The resulting code is really tiny...




回答4:


just sort both lists and compare result



来源:https://stackoverflow.com/questions/17474400/gprolog-simple-way-to-determine-whether-one-list-is-a-permutation-of-another

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!