在Prolog中实现一个简单的聚类算法

时间:2013-04-16 09:36:49

标签: prolog cluster-analysis

我的目标是在Prolog中实现一个简单的聚类算法。

假设您有一定数量的位置。每个位置都有一个ID,它们看起来像'位置(X,Y,ID)'。

我有以下想法:基本上你拿第一个ID及其位置,然后将这个ID添加到列表中。然后,您将此ID的位置与所有其他可能的位置进行比较。然后,每个具有与原始位置在一定距离内的位置的ID也被添加到列表中。下一步是对已添加到列表中的所有新ID执行完全相同的操作。然后,您对上一步中添加的ID执行相同的操作,等等。

在没有更多可能性之后,您将拥有一个形成群集的ID列表。例如1,2,3,6和8。

对于您在ID 1开始的第一个群集。通常,您将从ID 2开始并检查其中的群集。但它已经在第一个集群中,没有理由再次看这个集群。就像我们不必查看值3,6和8一样。因为我们只知道查看这些ID会给出完全相同的集群。

所以我怎么想象有一个谓词返回一个列表列表,基本上都是所有簇。该谓词使用另一个实际生成单个集群的谓词。

我已经尝试了一些东西,但它确实没有用。

可能的地点列表:

position(1,1,1)
position(3,2,2)
position(2,4,3)
position(4,6,4)
position(5,4,5)
position(10,3,6)
position(12,2,7)
position(13,6,8)
position(16,2,9)

假设距离为3,则应该有四个簇。 1,2,3,4和5; 6和7; 8; 9。

首先是一个简单的谓词,它检查两个ID是否在彼此的特定距离内:

checkDistance(ID1,ID2,Distance) :-
    position(X1,Y1,ID1),
    position(X2,Y2,ID2),
    abs(X2 - X1) =< Distance,
    abs(Y2 - Y1) =< Distance.

以下代码创建一个接近初始ID的ID列表:

compareAllPos(ID,Val,[],Distance) :- 
    \+ checkDistance(ID,Val,Distance).

compareAllPos(ID,Val,[Val|LS],Distance) :-
    checkDistance(ID,Val,Distance),
    Val2 is Val+1,
    compareAllPos(ID,Val2,LS,Distance).

它提供以下输出:

?- compareAllPos(1,1,List,3).
List = [1, 2, 3] ;

这只解决了第一部分。现在我必须浏览添加到列表中的每个值。我通过添加另一个compareAllPos来实现这一点:

compareAllPos(ID,Val,[Val|LS],Distance) :-
    checkDistance(ID,Val,Distance),
    Val2 is Val+1,
    append(LS1,LS2,LS),
    compareAllPos(Val,1,LS1,Distance),
    compareAllPos(ID,Val2,LS2,Distance).

下一个问题是程序现在陷入了无限循环,因此需要进行检查以确保尚未看到该ID。

我有以下想法:

compareAllPos2(ID,Val,_,[],Distance) :- 
    \+ checkDistance(ID,Val,Distance).

compareAllPos2(ID,_,Visited,[],_) :-
    member(ID,Visited).

compareAllPos2(ID,Val,Visited,[Val|LS],Distance) :-
    checkDistance(ID,Val,Distance),
    Val2 is Val+1,
    append(LS1,LS2,LS),
    append(ID,Visited,Visited1),
    compareAllPos2(Val,1,Visited1,LS1,Distance),
    append([],Visited1,Visited2),
    compareAllPos2(ID,Val2,Visited2,LS2,Distance).

我会使用以下行来调用它:

compareAllPos2(1,1,[],List,3).

这就是我遇到的问题。上面的代码无法正常工作,我还没有想出解决这个问题的正确方法。如果该代码有效,则应根据ID返回单个群集。最后一步是查看其他可能的集群。

1 个答案:

答案 0 :(得分:1)

添加最后一个compareAllPos / 4后,您将获得

?- compareAllPos(1,1,List,3).
List = [1, 2, 3] ;
List = [1, 2, 3, 1, 2, 3, 4, 5] ;
List = [1, 2, 3, 1, 2, 3, 4, 5] ;
List = [1, 2, 3, 1, 2, 3, 4, 5] ;
List = [1, 2, 3, 1, 2, 3, 4, 5] ;
List = [1, 2, 3, 1, 2, 1, 2, 3, 3|...] ;
List = [1, 2, 3, 1, 2, 1, 2, 3, 3|...] ;
...

我认为可能是循环的原因,你应该保持简单。我会写

checkDistance(ID1,ID2,Distance) :-
    position(X1,Y1,ID1),
    position(X2,Y2,ID2),
    ID1 \= ID2,
    abs(X2 - X1) =< Distance,
    abs(Y2 - Y1) =< Distance.

clusters(Distance, Cs) :-
    once(position(_,_,First)),
    clusters(Distance, First, [], Cs).

clusters(Distance, Pivot, SoFar, Cs) :-
    findall(Tentative,
        (   pos_not_clustered(SoFar, Tentative),
            checkDistance(Pivot, Tentative, Distance)
        ),
        Cluster),
    Updated = [[Pivot|Cluster]|SoFar],
    (   pos_not_clustered(Updated, NextPivot)
    ->  clusters(Distance, NextPivot, Updated, Cs)
    ;   Cs = Updated
    ).

pos_not_clustered(Clusters, ID) :-
    position(_,_,ID),
    maplist(not_member(ID), Clusters).

not_member(E, L) :- \+ memberchk(E, L).

因为它产生了

?- clusters(3,C).
C = [[9], [6, 7, 8], [4, 5], [1, 2, 3]].

编辑抱歉有错误的代码,我已经改为

clusters(Distance, Cs) :-
    once(position(_,_,First)),
    clusters(Distance, [[First]], Cs).

clusters(Distance, SoFar, Cs) :-
    (   pos_not_clustered(SoFar, Tentative),
        add_to_cluster(SoFar, Tentative, Distance, Updated)
    ->  clusters(Distance, Updated, Cs)
    ;   Cs = SoFar
    ).

pos_not_clustered(Clusters, ID) :-
    position(_,_,ID),
    maplist(not_member(ID), Clusters).

not_member(E, L) :- \+ memberchk(E, L).

add_to_cluster([C|Cs], Tentative, Distance, Updated) :-
    member(Id, C),
    checkDistance(Id, Tentative, Distance), !,
    Updated = [[Tentative|C]|Cs].
add_to_cluster([C|Cs], Tentative, Distance, [C|Rs]) :-
    add_to_cluster(Cs, Tentative, Distance, Rs).
add_to_cluster([], Tentative, _Distance, [[Tentative]]).

现在我得到了

?- clusters(3,C).
C = [[5, 4, 3, 2, 1], [8, 7, 6], [9]].