【问题标题】:Prolog: replace an element in a list at a specified indexProlog:替换列表中指定索引处的元素
【发布时间】:2026-01-29 00:55:02
【问题描述】:

我想要一个 Prolog 谓词,它可以替换列表中指定索引处的元素。

例子:

% replace(+List,+Index,+Value,-NewList).
?- L=[a,b,c,d], replace(L,1,z,L2).
L2 = [a,z,c,d]

我不知道该怎么做。谢谢你的帮助!洛伊克。

【问题讨论】:

  • 您尝试过什么,但没有成功?你尝试了什么?

标签: list prolog


【解决方案1】:

我会给你基本情况,我认为你应该能够轻松地做递归情况:

replace([_|T], 0, X, [X|T]).

编辑:

既然操作已经解决了,我将添加递归案例:

replace([H|T], I, X, [H|R]):- I > 0, I1 is I-1, replace(T, I1, X, R).

edit2:

正如@GeorgeConstanza 在 cmets 中询问的那样,这应该在超出范围的情况下返回原始列表:

replace([_|T], 0, X, [X|T]).
replace([H|T], I, X, [H|R]):- I > -1, NI is I-1, replace(T, NI, X, R), !.
replace(L, _, _, L).

如果有一个好的边界替换,它基本上是利用 cut 运算符来不到达第三个后备子句。

【讨论】:

  • replace([_|T],0,X,[X|T]). replace([H|T],I,X,[H|R]):-I1 is I-1, replace(T,I1,X,R). 谢谢 :)。
  • 请在递归规则中添加I > 0
  • @false 和其他 .. 如果索引超出范围,您将如何返回原始列表?我已经玩了一段时间了,但我无法弄清楚。试图存储列表的长度并使用 if else 但它给了我垃圾输出
  • 由于某种原因,我仍然得到“假”而不是返回的原始列表。例如:如果用户输入:replace([0,1,2,3,4], 6, 5555, X)。 -- 6 出界了。发生这种情况时,我一直试图让它返回原始列表
  • @GeorgeCostanza:我认为您的扩展没有意义。当给定一个太大的索引时,即使 arg/3 也会默默地失败,例如arg(1000,f(1),I). 对于负数,您可以考虑使用 domain_error(not_less_than_zero,-1)
【解决方案2】:

我想出的另一种方法,我认为是正确的(?)。我不知道运行时的复杂性。

replace(I, L, E, K) :-
  nth0(I, L, _, R),
  nth0(I, K, E, R).

用法:

?- replace(2, [1, 2, 3, 4, 5], 10, X).
X = [1, 2, 10, 4, 5].

【讨论】:

    【解决方案3】:

    fortran 的答案没问题,但是在 SWI-Prolog 结构中,结构具有无限的数量,所以这应该可以:

    replace([_|T], 0, X, [X|T]).
    replace([H|T], I, X, [H|R]) :-
        I > 0,
        I1 is I - 1,
        replace(T, I1, X, R).
    
    replace1(L, I, X, R) :-
        Dummy =.. [dummy|L],
        J is I + 1,
        nb_setarg(J, Dummy, X),
        Dummy =.. [dummy|R].
    
    tr(Method, K) :-
        length(L, K),
        K1 is K - 1,
        time(call(Method, L, K1, test, R)),
        assertion(nth1(K, R, test)).
    

    但是,令我惊讶的是:

    ?- % /home/carlo/prolog/replace.pl compiled 0,00 sec, 2,280 bytes
    ?- tr(replace,2000000).
    % 3,999,999 inferences, 2,123 CPU in 2,128 seconds (100% CPU, 1884446 Lips)
    true .
    
    ?- tr(replace1,2000000).
    % 5 inferences, 1,410 CPU in 1,414 seconds (100% CPU, 4 Lips)
    true.
    
    ?- tr(replace,4000000).
    % 7,999,999 inferences, 3,510 CPU in 3,520 seconds (100% CPU, 2279267 Lips)
    true .
    
    ?- tr(replace1,4000000).
    % 5 inferences, 2,825 CPU in 2,833 seconds (100% CPU, 2 Lips)
    true.
    
    ?- tr(replace,5000000).
    % 9,999,999 inferences, 3,144 CPU in 3,153 seconds (100% CPU, 3180971 Lips)
    true .
    
    ?- tr(replace1,5000000).
    % 5 inferences, 4,476 CPU in 4,486 seconds (100% CPU, 1 Lips)
    ERROR: =../2: Arguments are not sufficiently instantiated
    ^  Exception: (9) setup_call_catcher_cleanup(system:true, prolog_statistics:catch(user:call(replace1, [_G1, _G4, _G7, _G10|...], 4999999, test, _G15000005), _G15000021, (report(t(1324124267.2924964, 18.892632697, 28490132), 10), throw(_G15000021))), _G15000145, prolog_statistics: (_G15000032=true)) ? abort
    % Execution Aborted
    

    我的第一次尝试(K=10000000)杀死了这个进程! 所以,我不喜欢,试图获得一些性能,我最终向 SWI-Prolog 邮件列表填写了一个错误报告......

    编辑:在发布到 SWI-Prolog 邮件列表和(快速!)更正之后,我已经重建,这里是考虑内存使用提示的版本(现在全部ISO 标准代码!)。由于异常大的值,之前需要一个堆栈增长指令:

    ?- prolog_stack_property(global,limit(L)), N is L*2, set_prolog_stack(global,limit(N)).
    N = 536870912.
    

    这是更新的过程:

    replace2(L, I, X, R) :-
        Dummy =.. [dummy|L],
        J is I + 1,
        setarg(J, Dummy, X),
        Dummy =.. [dummy|R].
    

    和测试:

    ?- tr(replace,10000000).
    % 19,999,999 inferences, 5.695 CPU in 5.719 seconds (100% CPU, 3511942 Lips)
    true .
    
    ?- tr(replace2,10000000).
    % 5 inferences, 2.564 CPU in 2.571 seconds (100% CPU, 2 Lips)
    true.
    

    代码更快,但请注意Jan对我的邮件的评论:

    归结为 =..(+,-) 中的错误处理不当。固定的。顺便提一句。我 认为这是完成这项工作的非常可怕的方式。即使你想要 要这样做,只需使用 setarg/3 而不是 nb_setarg/3。这 后者确实应该是最后的手段。此方法使用更多内存 因为它需要巨大的术语和列表。最后,函子 (名称/数量对)当前未回收,因此您创建一个 这样的对象每次替换一个列表的长度,这个 从未使用过。

    【讨论】:

    • 试试this query,看看你在 SO 上提到这些结构的频率。
    【解决方案4】:

    显然,@fortran 使用递归替换是可行的方法。但这是否太疯狂/太慢而无法实际使用?

    replace(List, Idx, With, ListOut) :-
       length(Idx, Before),
       append(Before, After, List),
       (  After=[_Discard|Rest]
       -> true
       ;  Rest=[]
       ),
       append(Before, [With|Rest], ListOut).
    

    基本上,您创建一个大小为 Idx 的空白数组并将其绑定到输入列表。然后丢弃该项目,并将两个列表与夹在其中的替换元素绑定在一起。

    如果您尝试设置 N 元素列表的 idx N(从 0 开始索引),则可以进一步简化此操作。

    replace(List, Idx, With, ListOut) :-
       length(Idx, Before),
       append(Before, [_Discard|Rest], List),
       append(Before, [With|Rest], ListOut).
    

    【讨论】:

    • 它更慢:使用一千万个元素的列表,并替换最后一个,然后你的第二个版本在我的机器上需要 2.0 秒,fortran 的版本使用 1.1 秒,他的版本在第一个子句和从第二个删除的 I > 0 测试需要 1.0 秒。速度差异是否重要取决于您需要多久更换一次......
    【解决方案5】:

    如果我们使用same_length/2append/3length/2,我们不需要编写递归代码:

    list_nth0_item_replaced(Es, N, X, Xs) :- same_length(Es, Xs), append(前缀, [_|后缀], Es), length(前缀,N), 附加(前缀,[X|后缀],Xs)。

    OP 给出的示例查询:

    ?- list_nth0_item_replaced([a,b,c,d], 1, z, Xs).
       Xs = [a,z,c,d]
    ;  false.
    

    这也适用于“另一个方向”!

    ?- list_nth0_item_replaced(Xs, 1, z, [a,z,c,d]).
       Xs = [a,_A,c,d]
    ;  false.
    

    更好的是,我们甚至不需要指定具体的索引:

    ?- list_nth0_item_replaced(Es, N, X, [a,z,c,d]).
       N = 0, X = a, Es = [_A, z, c, d]
    ;  N = 1, X = z, Es = [ a,_A, c, d]
    ;  N = 2, X = c, Es = [ a, z,_A, d]
    ;  N = 3, X = d, Es = [ a, z, c,_A]
    ;  false.
    
    ?- list_nth0_item_replaced([a,b,c,d], N, X, Xs).
       N = 0, Xs = [X,b,c,d]
    ;  N = 1, Xs = [a,X,c,d]
    ;  N = 2, Xs = [a,b,X,d]
    ;  N = 3, Xs = [a,b,c,X]
    ;  false.
    

    【讨论】:

      【解决方案6】:

      真的,没有人永远应该使用任何相当长的 IMO 简单列表来执行此操作,因为每次更新都会占用 O(n) 新空间。通过setarg/nb_setarg 直接 set_once/update 将占用 0 新空间,并且使用列表的二叉树表示,O(log(n)) 新空间。替换也可以保存在一个单独的字典中,它本身作为一棵树维护(因为它需要成长)。 块列表(如in here)可以在树中保存大块,每个块都是固定大小的复合词,可通过setarg/nb_setarg 直接设置/更新,并根据需要将新块添加到树中。

      即使没有更新,访问普通列表也非常慢,O(n) 时间,让任何算法二次变得很容易。列表只是非常简短,或者作为家庭作业练习。

      【讨论】:

        【解决方案7】:

        in this previous answer 提供的代码非常通用——感谢

        有缺点吗?是的,有一个缺点:效率低下!

        在这个答案中,我们提高了性能保留了多功能性。

        :- use_module(library(clpfd))。

        我们像 this previous answer 定义谓词 fd_length/2 时所做的那样:

        list_nth0_item_replaced__NEW(Es, N, X, Xs) :-
           list_index0_index_item_replaced(Es, 0,N, X, Xs).
        
        list_index0_index_item_replaced([_|Es], I ,I, X, [X|Es]).
        list_index0_index_item_replaced([E|Es], I0,I, X, [E|Xs]) :-
           I0 #< I,
           I1 #= I0+1,
           list_index0_index_item_replaced(Es, I1,I, X, Xs).
        

        那么……它变得更快了吗?

        ?- numlist(1,100000,Zs), time(list_nth0_item_replaced(Zs,99999,x,Xs))。 % 14,499,855 次推断,0.893 秒内 0.893 CPU(100% CPU,16237725 唇) Zs = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], Xs = [1, 2, 3, 4, 5, 6, 7, 8, 9|...] ; % 7 次推理,0.000 CPU 在 0.000 秒内(99% CPU,18377 唇) 错误的。 ?- numlist(1,100000,Zs), time(list_nth0_item_replaced__NEW(Zs,99999,x,Xs))。 % 499,996 次推断,0.049 秒内 0.049 CPU(100% CPU,10158710 唇) Zs = [1, 2, 3, 4, 5, 6, 7, 8, 9|...], Xs = [1, 2, 3, 4, 5, 6, 7, 8, 9|...] ; % 6 次推断,0.000 CPU 在 0.000 秒内(93% CPU,213988 唇) 错误的。

        好的,它更快。但它仍然是通用的吗?

        ?- list_nth0_item_replaced__NEW([a,b,c,d], 1, z, Xs)。 Xs = [a,z,c,d] ;错误的。 ?- list_nth0_item_replaced__NEW(Xs, 1, z, [a,z,c,d])。 Xs = [a,_A,c,d] ;错误的。 ?- list_nth0_item_replaced__NEW(Es, N, X, [a,z,c,d])。 N = 0, X = a, Es = [_A, z, c, d], ; N = 1, X = z, Es = [ a,_A, c, d] ; N = 2, X = c, Es = [a, z,_A, d], ; N = 3, X = d, Es = [ a, z, c,_A] ;错误的。 ?- list_nth0_item_replaced__NEW([a,b,c,d], N, X, Xs)。 N = 0, Xs = [X,b,c,d] ; N = 1, Xs = [a,X,c,d] ; N = 2, Xs = [a,b,X,d] ; N = 3, Xs = [a,b,c,X] ;错误的。

        我觉得不错!

        【讨论】:

          【解决方案8】:

          像这样直截了当的方式呢?

          :- use_module(library(clpfd))。 list_nth0_item_replaced([_|Xs], 0, E, [E|Xs])。 list_nth0_item_replaced([X|Xs], N, E, [X|Ys]) :- N#> 0, N #= N0+1, list_nth0_item_replaced(Xs, N0, E, Ys)。

          这是 OP 指定的用例:

          ?- list_nth0_item_replaced([a,b,c,d],1,z,Ls).
             Ls = [a,z,c,d]
          ;  false.
          

          上面的代码是纯粹的,所以我们可以提出更一般的查询——并期待逻辑上合理的答案:

          ?- list_nth0_item_replaced([a,b,c,d], N, X, Ls).
             N = 0, Ls = [X,b,c,d]
          ;  N = 1, Ls = [a,X,c,d]
          ;  N = 2, Ls = [a,b,X,d]
          ;  N = 3, Ls = [a,b,c,X]
          ;  false.
          

          【讨论】:

            最近更新 更多