【问题标题】:How to try new permutations if previous solutions did not meet some conditions?如果以前的解决方案不满足某些条件,如何尝试新的排列?
【发布时间】:2016-05-31 03:01:46
【问题描述】:

所以,这是一个我已经尝试解决了一段时间的练习。我得到一个像[a-b,b-c] 这样的输入列表,它们是节点和连接节点的弧。条件是:

节点需要有一个唯一的关联编号,从1到N,

并且弧需要有一个唯一的关联数字,从 1 到 N-1,并且该数字必须是减去弧连接的节点的结果。

所以答案是:

EnumNodos = [enum(3,a), enum(1,b), enum(2,c)],
EnumArcos = [enum(2,a‐b), enum(1,b‐c)]

因此,由于我无法为此提出算法,也不知道是否有算法,所以我想,我可以尝试每一种可能性,因为我知道如果输入是正确的,那样我迟早会得到它。

我发现了一个 prolog 排列的例子,如果我给它一些输入列表,它会给出它的排列。然后(在控制台中),如果我点击 ';'它给了我另一个,依此类推。我正在尝试自己包含该代码。

我还没有完成,但我会提供一些帮助,特别是我尝试置换选项的“循环”方法。我真的不知道您会怎么说,在序言中,如果此操作失败,请尝试另一种新的排列,与之前尝试过的每个排列不同(不提供';'作为解决方案的输入。因为有很多排列正在进行要失败,我想检查它是否失败,如果失败,请尝试另一个。

EDIT 所以我刚刚发现了 setof... 我一直在尝试它,但我认为我仍然缺少一个关键部分。截至目前,我觉得我可以通过以下方式获得我需要的所有可能性的列表:setof(Out,perm(ListaEnum, SalidaPerm),X),

但是我仍然对失败然后重试的想法有疑问。到目前为止,我的想法是:我得到 X 结果,然后像对待任何列表一样进行旅行。我检查它是否有唯一的数字等等,如果没有,我想继续旅行那个X。所以我会努力失败而不是成功?我应该这样做吗??

% enumerate(CONNECTIONS_IN, NODES_OUT, ARCS_OUT)
%TODO query example of call: enumerate([a-b,b-c], EnumNodos, EnumArcos).

enumerate(C, EnumNodos, EnumArcos) :-
    enum_nodes(C, [], NodeListUnique, [], PermNodes, 1),
    loopPerm(C, NodeListUnique, EnumArcos, PermNodes, SalidaPerm).

% enum_nodes(CONNECTIONS_IN, NODES_IN, NODES_OUT, IDS_IN, IDS_OUT, START_ID) 
% Fills up NODES_OUT with unique nodes, and PERMOUT with IDS. New IDs start at START_ID...

enum_nodes([], N, N, M, M, _).
enum_nodes([A-B | T], N, NOUT, M, PERMOUT, ID) :-
    ensure_node(A, N, NTMP1, M, PERMNODESOUTA, ID, ID1),
    ensure_node(B, NTMP1, NTMP2, PERMNODESOUTA, PERMNODESOUTB, ID1, ID2),
    enum_nodes(T, NTMP2, NOUT, PERMNODESOUTB, PERMOUT, ID2).

% ensure_node(NODE, NODES_IN, NODES_OUT,IDS_IN, IDS_OUT, ID_IN, ID_OUT)
% Adds enum(ID_IN, NODE) to NODES_IN to produce NODES_OUT if NODE does not already exist in NODES_IN

ensure_node(NODE, NODES_IN, NODES_IN, PermNodesNOVALENin, PermNodesNOVALENin, ID, ID) :-
    member(NODE, NODES_IN), !.

ensure_node(NODE, NODES_IN, [NODE | NODES_IN], PERMIN, [ID_IN|PERMIN], ID_IN, ID_OUT) :-
    ID_OUT is ID_IN + 1.

%At this point I have a list of unique nodes and a list of IDs for said nodes, and I want to start calculatin permutations of this two lists until I find one that works.
loopPerm(C, NodeListUnique, EnumArcos, PermNodes, SalidaPerm):-
    crearEnum(NodeListUnique, PermNodes, ListaEnum),
    perm(ListaEnum, SalidaPerm),
    create_arcs(C, SalidaPerm, []),
    %%here is where code stops working properly
    loopPerm(C, EnumNodos, EnumArcos, PermNodes, SalidaPerm).

%crear_enum(NODES_IN, IDS_IN, enumLISTout)
%creates a list of enums to be permuted, TODO the idea here is that each call will change the list, so if it failed before, it should try a new one.
crearEnum([], [], []).
crearEnum([H1 | NodeListUnique], [H2| PermNodes], [enum(H2,H1)|Salida]):-
    crearEnum(NodeListUnique, PermNodes, Salida).


% create_arcs(CONNECTIONS_IN, NODES_IN, ARCS_OUT).  
% Create arcs - makes a list of arc(NODE_ID_1, NODE_ID_2)...

create_arcs([], _, _).
create_arcs([A-B | T], NODES, LISTARCS) :-
    ensure_arcs(A,B,NODES, LISTARCS, LISTARCS2),
    create_arcs(T, NODES, LISTARCS2).

%ensure_arcs(NODE_A, NODE_B, NODELIST, LISTARCSIN, LISTARCSOUT)
%builds a list of arcs TODO works WRONG when arc already was in the input. It should fail, but it just checks that is a member and moves on. So basically it works when arcs are new, because they are added properly, but not when arcs were already found (and as per the exercise it should fail and try another permutation).
ensure_arcs(A,B,NODES, LISTARCSIN, LISTARCSIN):-
    member(enum(NODE_ID_A, A), NODES),
    member(enum(NODE_ID_B, B), NODES),
    REMAINDER is abs(NODE_ID_A-NODE_ID_B),
    member(enum(REMAINDER,_), LISTARCSIN), !.

ensure_arcs(A,B,NODES, LISTARCSIN,[enum(REMAINDER, A-B) | LISTARCSIN]):-
    member(enum(NODE_ID_A, A), NODES),
    member(enum(NODE_ID_B, B), NODES),
    REMAINDER is abs(NODE_ID_A-NODE_ID_B).


perm([H|T], Perm) :-
   perm(T, SP),
   insert(H, SP, Perm).
perm([], []).

insert(X, T, [X|T]).
insert(X, [H|T], [H|NT]) :-
   insert(X, T, NT).

以下是我手工编写的其他一些示例,以备不时之需。我也想道歉,因为我对代码一点也不满意,只是我需要继续前进,而不是修复我确定是痛苦的错误(但我无法真正修复,截至现在,我需要很长时间才能获得任何有效的代码,即使几乎没有)。

    6a
  5    4
1b      2e
2       3
3c      5f
1
4d

 EnumNodos = [enum(6,a), enum(1,b), enum(2,e), enum(3,c), enum(5,f), enum(4,d)],

EnumArcos = [enum(5,a‐b), enum(4,a-e), enum(3,e-f), , enum(2,b-c), enum(1,c-d)]


    5a
  4   3
1b      2e
1       2
3c      4f

EnumNodos = [enum(5,a), enum(1,b), enum(2,e), enum(3,c), enum(4,f)],

EnumArcos = [enum(4,a‐b), enum(3,a-e), enum(1,b-c), , enum(2,e-f)]

    5a
  4    3
1b      2e
2
3c      
1
4d

【问题讨论】:

    标签: algorithm prolog clpfd


    【解决方案1】:

    简短的回答是,您想要做的正是 Prolog自动所做的:它被称为 回溯,意味着 Prolog 将尝试所有可能性 直到他们都筋疲力尽。

    因此,在您的情况下,您可以将整个任务从概念上表述为:

    解决方案(S): - 候选人), 满足_all_constraints(S)。

    就是这样。 Prolog 将生成所有候选解决方案并准确报告那些“满足所有约束”的解决方案,这就是您需要在satisfies_all_constraints/1 中描述的内容@

    因此,Prolog 被称为声明性语言:您描述了您希望它找到的什么。您并不特别关心它如何找到这些解决方案。

    请注意,通过使用setof/3,您可以规避这种隐式回溯,并将所有解决方案具体化为一个 Prolog 术语,您可以在 in 程序中进行推理。例如,如果您想以不同的顺序探索分支,或者完全 Prolog 中实施其他搜索策略,这可能很有用。对于幼稚的方法,不需要setof/3,而是需要清楚地描述您想要找到的解决方案。

    更长的答案是,这种方法(也称为“生成和测试”)在某些情况下绝对非常吸引人、有用且具有指导意义,例如:

    1. 您完全不知道在任何情况下如何找到您的结果
    2. 无论如何您都想生成所有解决方案
    3. 在任何情况下生成所有可能性都不会花费很多时间

    但是,通常有更快的方法可以找到特定的解决方案,而您的问题(尽管我不知道您真正想要找到什么)似乎属于这一类。

    不过,由于这也是您所要求的,我建议您至少尝试天真的方法(生成和测试),因为它通常会导致您的内容非常简短和清晰确实想要,并且运气好的话,也足以得到它,至少在很小的情况下。

    【讨论】:

    • 我知道回溯,但是对于任何超过两行的代码,我无法正确地看到它是如何做到的,大多数时候,尤其是在这种情况下,我可以'根本看不到。老实说,如果我看不到代码在失败并返回后会做什么,我不知道编程的方法。也许可以做到,但我不知道。而且我可能抱怨太多了,但在这种情况下我根本不知道如何生成和测试。
    • 试着这样想:假设我已经构建了一个解决方案。您能否在 Prolog 中准确地写下解决方案满足哪些条件,以便您可以说“是的,这确实是一个解决方案”?请关注任何解决方案所展示的属性的这种声明性描述。您会发现这对您找到这样一个解决方案的实际目标有很长的路要走。事实上,在 Prolog 中编程时,通常只需说明解决方案所展示的属性,然后让 Prolog 完成其余的工作。
    • 如果您不介意,我想对此发表两点评论。我确实知道条件,但截至目前,在序言中,我知道怎么说“如果确实发生了这种情况,请继续浏览列表”。而现在我遇到了相反的情况:“如果这没有发生,请继续浏览列表,因为您还没有找到答案”。基本上,我需要遍历我的排列列表,直到找到符合要求的排列。我不知道该怎么做。
    • 这是一种非常必要的思考任务的方式,在 Prolog 中编程不会让你走得太远。这听起来可能很便宜,但我向你保证这是真的:你只能用命令式编程走这么远。认真考虑一下:例如,假设我为您提供了您正在寻找的术语的部分实例化解决方案。考虑任务的必要方式是否仍然涵盖这一点?很可能不是。不要说“如果这确实发生了”;说:“这什么时候成立?”描述表征解决方案的条件。花点时间思考一下吧!
    • 完全没问题!你肯定需要一些时间来考虑这种关系,因为它会导致比你现在习惯的更通用的程序。我将在其他线程中回答您的后续问题。
    【解决方案2】:

    除了我在另一个答案中写的内容之外,我现在想向您展示一个适合您任务的具体解决方案。最多只做很小的修改,它就可以在 SICStus Prolog、YAP、GNU 和 SWI 中运行:

    graph_enumeration(节点,弧,NPs-APs,Vs):- 长度(节点,N), pair_keys_values(NPs,节点,VNs), pair_keys_values(AP,弧,VA), all_distinct(VNs), all_distinct(VA), VNs ins 1..N, N1 #= N - 1, VAs ins 1..N1, 附加(VNs,VAs,Vs), 地图列表(arc_connects(NPs),APs)。 arc_connects(NPs, arc(N1,N2)-V) :- 成员(N1-NV1,NPs), 成员(N2-NV2,NPs), V #= 绝对值(NV1 - NV2)。

    注意关键思想:

    1. 我们描述了一个解决方案的样子,即,对解决方案有什么影响
    2. 我们使用 CLP(FD) 约束以声明方式描述需求
    3. 我们通过保持在 Prolog 的纯单调子集内使解决方案非常通用。没有不纯的!/0,没有 if-then-else,没有废话!只需描述存在什么,然后让 Prolog 完成剩下的工作以找到实际的解决方案。

    示例查询和答案,用于您的初始测试用例:

    ?- graph_enumeration([a,b,c], [arc(a,b),arc(b,c)], E, Vs)。 E = [a-_G6158, b-_G6164, c-_G6170]-[arc(a, b)-_G6176, arc(b, c)-_G6185], Vs = [_G6158,_G6164,_G6170,_G6176,_G6185], _G6158 在 1..3 中, _G6232+_G6164#=_G6158, all_distinct([_G6158, _G6164, _G6170]), _G6164 在 1..3 中, _G6273+_G6170#=_G6164, _G6170 在 1..3 中, _G6273 在 -2.. -1\/1..2, _G6185#=abs(_G6273), _G6185 在 1..2, all_distinct([_G6176, _G6185]), _G6176 在 1..2 中, _G6176#=abs(_G6232), _G6232 在 -2.. -1\/1..2 ; 错误的。

    剩余目标表示这是一个有条件的答案。有一个解决方案iff可以满足剩余约束。

    为了找到具体的解决方案,我们使用枚举谓词label/1。例如:

    ?- graph_enumeration([a,b,c], [arc(a,b),arc(b,c)], E, Vs), label(Vs)。 E = [a-1, b-3, c-2]-[arc(a, b)-2, arc(b, c)-1], Vs = [1, 3, 2, 2, 1] ; E = [a-2, b-1, c-3]-[arc(a, b)-1, arc(b, c)-2], Vs = [2, 1, 3, 1, 2] ; E = [a-2, b-3, c-1]-[arc(a, b)-1, arc(b, c)-2], Vs = [2, 3, 1, 1, 2] ; E = [a-3, b-1, c-2]-[arc(a, b)-2, arc(b, c)-1], Vs = [3, 1, 2, 2, 1] ; 错误的。

    这为您提供了在这种情况下存在的所有解决方案。再次注意,Prolog 自动为我们在回溯时生成所有解决方案。另请注意,我们可以在所有方向上使用谓词。例如,我们可以验证解决方案,并完成部分实例化的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-29
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      • 2018-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多