【问题标题】:How do you append an element to a list in place in Prolog?如何将元素附加到 Prolog 中的列表中?
【发布时间】:2013-02-08 08:17:26
【问题描述】:

如果我在 Prolog 中有一个列表,例如 X = [1, 2, 3, 4],我如何将元素 5 添加到列表的末尾以使 X = [1, 2, 3, 4, 5]?

append 函数需要两个列表,即 append(A,B,C) 将 A 和 B 连接到列表 C。

我可以用一个临时列表 Y = [1, 2, 3, 4] 和 Z = [5] 来做这个,然后做一个追加(Y,Z,X),但我不喜欢有一个临时清单。

通常的免责声明适用于此 - 这不是家庭作业,我只是在学习 Prolog。

【问题讨论】:

  • 简短回答:你没有;你根本不知道。

标签: list prolog append in-place difference-lists


【解决方案1】:

既然 Prolog 有 append 只接受列表,我们为什么不使用它来插入我们的元素到列表之一。即

% E = element, L = list, R = result
% e.g. add_elem_in_list ([1,2,3,4], 5, R).
add_elem_in_list(L, E, R) :- append(L, [E], R).

【讨论】:

    【解决方案2】:

    你不能在 Prolog 中修改列表,但是你可以创建一个未指定长度的列表:

    main :-
        A = [1,2,3,4|_].
    

    然后,您可以在 SWI-Prolog 中使用nth0/3 插入一个元素:

    :- initialization(main).
    
    main :-
        A = [1,2,3,4|_],
        nth0(4,A,5),
        writeln(A).
    

    插入此元素后,A = [1,2,3,4,5|_]

    您还可以定义一个函数,将项目附加到列表的末尾,然后像这样使用它:

    :- initialization(main).
    
    append_to_list(List,Item) :-
        List = [Start|[To_add|Rest]],
        nonvar(Start),
        (var(To_add),To_add=Item;append_to_list([To_add|Rest],Item)).
    
    main :-
        A = [1,2,3|_],
        append_to_list(A,4),
        append_to_list(A,4),
        writeln(A).
    

    在本例中,A = [1,2,3,4,4|_] 在这两项后附加。

    【讨论】:

    • 您还可以添加,在每次追加时从头开始重新跟踪打开列表将是二次的,因此我们可以为 O(1) 追加显式维护 end var - 这将是那么差异列表。另外,另一种方法是保持已知长度并直接使用nth0/nth1
    【解决方案3】:

    一种声明性解决方案是使用差异列表(正如丹尼尔在其回答中所建议的那样)。差异列表的名称通常表示为两个列表之间的差异:列表及其尾部。例如,一个空列表可以表示为T-T。包含元素 1、2 和 3 的列表可以表示为 [1,2,3| T]-T(注意 (-)/2 是标准的内置中缀运算符)。这种表示的优点是您可以通过使用 append/3 谓词的单个事实定义在恒定时间内将元素附加到列表中:

    append(L1-T1, T1-T2, L1-T2).
    

    一个用法示例:

    ?- append([1,2,3,4| T1]-T1, [5| T2]-T2, Result).
    T1 = [5|T2],
    Result = [1, 2, 3, 4, 5|T2]-T2.
    

    如果需要,在“正常”列表和差异列表之间进行转换并不难。我把它作为练习留给你。

    【讨论】:

    • 刚刚注意到这是一个非常古老的问题(互联网时代)!
    • s(X):坚如磐石。
    【解决方案4】:

    正如其他人所指出的,您将陷入性能问题。
    但作为一个练习,我决定尝试创建一个谓词,它可以将一个元素附加到列表的末尾,而不使用append

    % add_tail(+List,+Element,-List)
    % Add the given element to the end of the list, without using the "append" predicate.
    add_tail([],X,[X]).
    add_tail([H|T],X,[H|L]):-add_tail(T,X,L).
    

    我建议您直接使用 append 函数,作为内置函数,它可能比任何手动制作的函数都要快。

    【讨论】:

    • 本杰明·富兰克林的风格:“那些为了购买一点临时性能而放弃基本正确性的人,既不应该得到正确性,也不应该得到表现。
    • add_tail(X,[L1],[L2])add_tail(X,[Y|L1],[Z|L2]) 之间有什么区别?
    • [Head1, Head2|Tail] 将从列表的前面删除前 2 个变量,并将其余变量放在变量 Tail 中的单独列表中。所以 [1,2,3,4] 意味着 Head1 = [1],Head2 = [2],Tail = [3,4],这意味着您可以通过递归删除列表的前面元素。 add_tail 内容如下 1. 如果第一个列表为空并且 X 现在是 outputList 的最后一个元素,则停止搜索替代项。 2.从inputList中取第一个变量,传入X进行校验,当1.为真时递归统一H到outputList的前面。 1. 确保后退。
    • 基本上,从 List1 中删除所有元素,将 X 添加到一个空列表中,然后将 List1 中的所有元素以与删除相反的顺序添加回 List2,创建您通常无法创建的 [List1|X]这样做是因为 X 将根据现有的 Tail 检查并失败。
    【解决方案5】:

    Prolog 中的变量只能赋值一次。只要 X 具有值 [1,2,3,4],它就永远不会有另一个值。就像你提到的那样,一个临时变量和 append/3 是这样做的。

    话虽如此,你可以做一个可能不推荐的技巧。如果 X = [1,2,3,4,Y] 那么你可以做 Y=5 并且 X 现在有你想要的值。我相信这种技术被称为差异列表。

    【讨论】:

    • @no-one-in-specific 将 Prolog 变量视为数学变量而不是存储位置。我知道这是一个重大的范式转变,但是当你得到它时,Prolog 中的任何事情都会很容易(或至少合乎逻辑)。
    • @mndrix - 很好的答案。这清楚地想到了。所以如果我理解正确的话,如果我说 X=[A,B,C,D] 然后再赋值 A=[1], B=[2], 那么 X=[[1], [2], C, D]。然后,如果我指定 C=[3], D=[4],那么我最终有 X=[[1],[2],[3],[4]] 并且它是固定不变的。跨度>
    • 为了使用差异列表,您需要跟踪列表中的“洞”(在这种情况下,它的尾部)。即差异列表始终是一对两个术语,一个是带有“洞”的列表,另一个是“洞”本身。 “洞”本身就是一个逻辑变量。表示对的传统方式是使用内置的中缀运算符。
    • 不完全是。使用X = [1,2,3,4 | Y] 然后Y = [5 | Z] 差异列表技术:在X...Y 末尾附加5 得到加长的X...Z。前一个X...Y 是一个仍然有效,更短的差异列表,仍然代表与扩展之前相同的前缀1,2,3,4。差异列表很容易理解为 open-ended 列表或前缀。此外,以这种方式扩展差异列表是 O(1) 操作。
    • “不完全”是对“我相信这种技术被称为差异列表”的回应。在答案中。
    【解决方案6】:

    您担心问题的错误结局。结构共享只能通过将一个元素放在列表的开头来实现。该方法具有您想要的性能特征。由于列表的定义方式,当您附加两个列表时,将复制整个第一个列表。在这种情况下,这将是整个列表。单项列表产生的垃圾显然会比这小得多。

    如果您确实必须追加,请考虑向后构建列表,然后在末尾反转一次,这样更便宜,或者使用差异列表,这样可以有效地追加到末尾。

    【讨论】:

    • s(X) 用于经典的“双反转,前置项目”方法!
    猜你喜欢
    • 2015-02-05
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 1970-01-01
    • 2020-09-02
    • 2014-06-10
    相关资源
    最近更新 更多