【问题标题】:Prolog membership operation on a list which returns the index of the found element列表上的 Prolog 成员资格操作返回找到的元素的索引
【发布时间】:2022-12-13 02:48:07
【问题描述】:

我目前已经开始使用 PROLOG,我想编写一个谓词来检查给定对象是否在此列表中。如果对象在列表中,则谓词应返回元素的索引。如果未找到该元素,则应返回 0。

它应该像这样工作:find(3,[1,4,5,3,2,3],N). -> yes. N / 4 find(2,[1,3,4,5,6,7],N). -> yes. N / 0

但我在计算索引 N 时遇到问题,也许这里有人可以提供帮助。我见过很多关于如何遍历列表的不同方法,但我发现了很多不同的方法,但我无法理解它们是如何工作的。如果有人可以提供解决方案并解释其工作原理和原因,我将非常高兴。

这是我到目前为止写的:

find(X, [X|TAIL], N) :- N is 1, write(N).
find(X, [], N) :- N is 0, write(N).

find(X, [_|TAIL], N) :- find(X, TAIL, N + 1).

它适用于基本情况:

find(a, [a, b, c, d, e, f, g], N) -> yes. N / 1.
find(j, [a, b, c, d, e, f, g], N) -> yes. N / 0.

但是当它以递归开始时它不再工作了,我不明白出了什么问题。

对于递归情况,它给了我这个: find(b, [a, b, c, d, e, f, g], N) -> no.

我感谢每一个答案和每一个评论!

要求:

  • 如果一个元素不在列表中,它应该输出yes. N = 0. 例子:?- find(a, [], N) -> yes. N = 0.?- find(a, [b,c,d], N) -> yes. N = 0.
  • 如果一个元素在列表中,它应该输出该元素的索引(从 1 开始计数)。示例:?- find(a, [a, b, c], N) -> yes. N = 1?- find(a, [b,c,a,d], N) -> yes. N = 3.
  • 如果元素出现多次,它应该只输出元素第一次出现的索引。示例:?- find(a, [a,b,c,a], N) -> yes. N = 1.
  • 它应该总是只给出答案。
  • 如果可能,不应使用任何库。
  • 查询 ?- find(a, [a, b,c], 0)?- find(a, [b, a, c], 0) 以及列表中 a 的所有其他查询都应使用 no 回答。
  • 查询 ?- find(a, [a, b, c, a], 4) 应该用 no 回答,因为索引为 4 的 a 不是 a 的第一个出现。

【问题讨论】:

  • 我取得了一点进步:find(X, [], 0). find(X,[X|_], 1). find(X, [_|Xs], N) :- find(X, Xs, Rest), N is 1 + Rest.这段代码现在对列表中的元素有效,但是如果我想找到一个不在列表中的对象,N变成6而不是0。我怎么能解决这个问题?
  • find(a, [a,b,c,a,b,c], Index) 会发生什么?第一个结果 1,回溯 4,再次回溯 0 for not found 是我的代码所做的,但我不知道这是否有意义。
  • @TessellatingHeckler 我不太确定我是否理解正确。但是对于find(a, [a,b,c,a,b,c], Index),Prolog 应该给出以下答案:Yes. Index = 1。所以它应该返回给定原子第一次出现的索引。如果原子没有出现在列表中,它应该给出 0。感谢您的评论!
  • 这被称为 nth/3nth1/3,但没有这个 0-case。
  • 这个问题的“return 0”部分是否有充分的理由(从 Prolog 关系视图来看这是相当丑陋的)?简单的失败还不够吗? nth1/3 已经存在 - swi-prolog.org/pldoc/man?predicate=nth1/3

标签: functional-programming prolog


【解决方案1】:

笔记:此解决方案生成在回溯时找到 Item 的每个索引,并将 Index0 统一只要当没有项目与目标项目统一时,并且不完全是 OP 现在在他的要求中明确说明的内容。

item_index(Item, L, Index):-
  item_index(L, Item, 1, Index).
  
item_index([], _, _, 0).
item_index([Item|L], Item, CurIndex, Index):-
  item_index1(L, Item, CurIndex, Index).
item_index([CurItem|L], Item, CurIndex, Index):-
  dif(CurItem, Item),
  succ(CurIndex, CurIndex1),
  item_index(L, Item, CurIndex1, Index).

item_index1(_, _, Index, Index).
item_index1(L, Item, CurIndex, Index):-
  succ(CurIndex, CurIndex1),
  item_index2(L, Item, CurIndex1, Index).

item_index2([Item|L], Item, CurIndex, Index):-
  item_index1(L, Item, CurIndex, Index).
item_index2([CurItem|L], Item, CurIndex, Index):-
  dif(CurItem, Item),
  succ(CurIndex, CurIndex1),
  item_index2(L, Item, CurIndex1, Index).

解释:

这个答案通过不同的程序来维护是否已经找到任何解决方案的状态。所以当遍历item_index/4中的列表时,一直没有匹配。在第一次匹配之后(以及在每次进一步的匹配之后)item_index1/4 被调用,这将统一 Index 与当前索引,并在回溯时继续遍历 item_index2/4 中的列表。

当没有更多的项目要遍历时,只有在没有以前的匹配项时,它才会将 Index 统一为 0(这是在 item_index/4 的第一个子句中完成的)。

样本运行:

?- item_index(A, [a,b,c,d,a,b,c], X).
A = a,
X = 1 ;
A = a,
X = 5 ;
A = b,
X = 2 ;
A = b,
X = 6 ;
A = c,
X = 3 ;
A = c,
X = 7 ;
A = d,
X = 4 ;
X = 0,
dif(A, a),
dif(A, c),
dif(A, b),
dif(A, a),
dif(A, d),
dif(A, c),
dif(A, b).

?- item_index(d, [a,b,c], X).
X = 0.    

?- item_index(A, [a,b,a], X).
A = a,
X = 1 ;
A = a,
X = 3 ;
A = b,
X = 2 ;
X = 0,
dif(A, a),
dif(A, a),
dif(A, b).

?- item_index(a, [a,b,C], X).
X = 1 ;
C = a,
X = 3 ;
false.

【讨论】:

  • 这对我有用,但如果我询问查询 ?- item_index(a, [a, b, c, d, a], N).,我会得到 yes. N = 1no. 作为答案。而且我在真正理解代码方面遇到了很多问题。您能否稍微解释一下您的思考过程,以便我可以更好地理解代码?
  • 您的查询应该产生N = 1,然后在回溯(按;)时应该产生N = 5。再次按; 会给出 false,因为没有更多的解决方案。
  • @DavidKrell:编辑答案以添加代码工作原理的解释
  • 它应该只找到元素的第一次出现。所以查询 ?- item_index(a, [a,b,c,d,a], 5). 应该给出 no. 作为答案。
  • 如第一段所述,此答案会生成找到 Item 的每个索引。如果您想提交第一个答案,那么您可以将 item_index/3 更改为 item_index(Item, L, Index):- item_index(L, Item, 1, FirstIndex), !, Index=FirstIndex.,尽管这可能会为非地面 Item/L 提供不正确的结果。
【解决方案2】:

使用库 reif 的两个纯解决方案:

first_index(E0, Es, N) :-
    first_index_(E0, Es, 0, N).

first_index_(_, [], _, none).
first_index_(E0, [E|Es], I0, N) :-
    if_(
        E0 = E,
        N = some(I0),
        (   succ(I0, I),
            first_index_(E0, Es, I, N)
        )
    ).

index(E0, Es, none) :-
    maplist(dif(E0), Es).
index(E0, Es, some(I)) :-
    nth0(I, Es, E0).

此查询确定性地成功:

?- L = [a,a], N = some(0), first_index(a, L, N).
   L = "aa", N = some(0).

此查询失败:

?- L = [a,a], N = some(1), first_index(a, L, N).
   false.

使用index/3时:

?- L = [a,a], index(a, L, some(0)), index(a, L, some(1)).
   L = "aa".

问题:

find_first(E, Es, N) :-
    first_index(E, Es, I),
    conversion(I, N).

find(E, Es, N) :-
    index(E, Es, I),
    conversion(I, N).

conversion(none, 0).
conversion(some(I), N) :-
    succ(I, N).

现在我没有更好的名字。

【讨论】:

  • 谢谢您的回答!你能不能解释一下这些问题?如果我给出以下查询:first_index(a, [b, a, c], N) 答案是no. 但它应该给出yes. N = 2. 或者我应该如何制定查询以便它给我正确的答案?
  • 哦,好吧,我会尽力做到这一点。但直到现在,我从未在 PROLOG 中使用过库,我只是使用了自己的谓词和递归,也许有一种方法可以在不使用库的情况下解决这个问题?
  • 如果我复制并粘贴它并向其提供以下查询:?- first_index(a, [a,b,c], N),我收到一条错误消息,指出程序if_/3 未知。如果我给它以下查询:?- index(a, [a,b,c],N),我得到N = some(0)。不过应该是N = 1
  • 我正在使用 SWI Prolog(AMD64,多线程,版本 9.0.0)
  • reif:- use_module(reif)
【解决方案3】:

使用nth1/3的参数顺序

nth1_once_else_0(Nth1, Lst, Elem) :-
    nth1_once_else_0_thaw_(Lst, Elem, 1, Nth1).

nth1_once_else_0_thaw_(L, _Elem, _Upto, Nth1) :-
    L == [],
    !,
    Nth1 = 0.
nth1_once_else_0_thaw_(L, Elem, _Upto, Nth1) :-
    Nth1 == 0,
    !,
    nth1_once_else_0_thaw_0_(L, Elem).
nth1_once_else_0_thaw_([H|_], Elem, Upto, Nth1) :-
    Upto == Nth1,
    !,
    H = Elem.   
nth1_once_else_0_thaw_(_L, _Elem, Upto, Nth1) :-
    % If gone past Nth1, fail
    nonvar(Nth1),
    Nth1 =< Upto,
    !,
    fail.
nth1_once_else_0_thaw_(L, Elem, Upto, Nth1) :-
    var(L),
    var(Nth1),
    % Too little info
    !,
    when(
        (nonvar(L) ; nonvar(Nth1)),
        nth1_once_else_0_thaw_(L, Elem, Upto, Nth1)
    ).
nth1_once_else_0_thaw_(L, _Elem, _Upto, Nth1) :-
    L == [],
    !,
    Nth1 = 0.
nth1_once_else_0_thaw_([H|_], Elem, Upto, Nth1) :-
    ground(Elem),
    ground(H),
    H = Elem,
    !,
    % Elements match
    Upto = Nth1.
nth1_once_else_0_thaw_([H|T], Elem, Upto, Nth1) :-
    (   nonvar(Nth1),
        Nth1 > Upto -> true
    ;   + subsumes_term(H, Elem)
    ),
    !,
    % Elements must be different
    dif(H, Elem),
    Upto1 is Upto + 1,
    nth1_once_else_0_when_(T, Elem, Upto1, Nth1).
nth1_once_else_0_thaw_([H|T], Elem, Upto, Nth1) :-
    % Wait for ground variables
    when(
        (ground(H), ground(Elem) ; nonvar(Nth1)),
        nth1_once_else_0_thaw_([H|T], Elem, Upto, Nth1)
    ).

nth1_once_else_0_when_(L, Elem, Upto, Nth1) :-
    var(L),
    !,
    when(
        (nonvar(L), nonvar(Elem) ; nonvar(Nth1)),
        nth1_once_else_0_thaw_(L, Elem, Upto, Nth1)
    ).
nth1_once_else_0_when_([], _Elem, _Upto, 0).
nth1_once_else_0_when_([H|T], Elem, Upto, Nth1) :-
    when(
        (nonvar(H), nonvar(Elem) ; nonvar(Nth1)),
        nth1_once_else_0_thaw_([H|T], Elem, Upto, Nth1)
    ).

% Remainder of list does not match
nth1_once_else_0_thaw_0_(L, Elem) :-
    var(L),
    !,
    freeze(L, nth1_once_else_0_thaw_0_(L, Elem)).
nth1_once_else_0_thaw_0_([], _Elem).
nth1_once_else_0_thaw_0_([H|T], Elem) :-
    dif(H, Elem),
    freeze(T, nth1_once_else_0_thaw_0_(T, Elem)).

使用 swi-prolog 的 unit test 能力:

:- begin_tests(nth1_once_else_0).

test(1) :-
    nth1_once_else_0(2, [a, b], B), B == b.
test(2) :-
    nth1_once_else_0(N, [a, b, c, d, e, f, g], a), N == 1.
test(3) :-
    nth1_once_else_0(N, [a, b, c, d, e, f, g], j), N == 0.
test(4) :-
    nth1_once_else_0(N, [a, b, c, d, e, f, g], b), N == 2.
test(5) :-
    nth1_once_else_0(N, [], a), N == 0.
test(6) :-
    nth1_once_else_0(N, [], _), N == 0.
test(7) :-
    nth1_once_else_0(N, [a, b, c, a, b, c], a), N == 1.
test(8) :-
    + nth1_once_else_0(6, [a, b, c, a, b, c], c).
test(9) :-
    nth1_once_else_0(N, L, b), L = [a, b], N == 2.
test(10) :-
    + nth1_once_else_0(0, [a, b], b).
test(11) :-
    nth1_once_else_0(N, [a, b, c, a], a), N == 1.
test(12) :-
    nth1_once_else_0(N, [a, b, c], d), N == 0.
test(13) :-
    nth1_once_else_0(N, [a, b], X), X = b, N == 2.
test(14) :-
    nth1_once_else_0(N, L, X), L = [a, b|_], X = b, N == 2.
test(15) :-
    nth1_once_else_0(N, L, X), L = [a, b|_], N = 2, X == b.
test(16) :-
    nth1_once_else_0(N, L, X), L = [a, b|_], X = z, N = 0.
test(17) :-
    L = [a, a], nth1_once_else_0(1, L, a), + nth1_once_else_0(2, L, a).
test(18) :-
    nth1_once_else_0(N, L, X), L = [a, b|_], L = [a,b,c], N = 0, + X = a.
test(19) :-
    nth1_once_else_0(N, L, X), L = [a, b|_], X = z, L = [a, b], N == 0.
test(20) :-
    nth1_once_else_0(N, L, X), nth1(2, L, b), nth1(1, L, a), X = z, L = [a, b], N == 0.
test(21) :-
    nth1_once_else_0(N, L, X), nth1(2, L, b), nth1(1, L, a), L = [a, b], X = a, N == 1.
test(22) :-
    nth1_once_else_0(N, L, X), X = a, nth1(2, L, b), nth1(1, L, a), N == 1.
test(23) :-
    nth1_once_else_0(N, L, X), X = b,  nth1(2, L, b), nth1(1, L, a), N == 2.
test(24) :-
    nth1_once_else_0(3, L, c), nth1(3, L, C), C == c.
test(25) :-
    nth1_once_else_0(3, L, C), C = c, nth1(3, L, Z), Z == c.
test(26) :-
    nth1_once_else_0(N, L, c),  N = 3, nth1(N, L, C), C == c.
test(27) :-
    nth1_once_else_0(N, L, C), C = c, N = 3, nth1(N, L, Z), Z == c.
test(28) :-
    nth1_once_else_0(N, [a, b, c|_], c), N == 3.
test(29) :-
    + nth1_once_else_0(3, [_, _], z).
test(30) :-
    nth1_once_else_0(N, [-_, -_], +_), N == 0.

:- end_tests(nth1_once_else_0).

结果:

?- run_tests.
% All 30 tests passed

【讨论】:

  • 谢谢您的回答!这个答案满足所有要求,也没有我以前的解决方案错误指出的问题。因此,我给你接受的答案。您能解释一下 when(nonvar(H) ... 部分吗?我不熟悉这个谓词是如何工作的。
  • 整个事情比我想象的要复杂得多。感谢您的工作!
  • 更新。它通过了我的测试。
  • 再次更新,通过27项测试。
  • 非常复杂,而且仍然很弱:?- nth1_once_else_0(Nth, [-_,-_], +_). 应该会立即失败。
【解决方案4】:

使用描述性谓词名称:

nth1_once_else_0(Elem, Lst, Nth1) :-
    % Start at element 1
    nth1_once_else_0_(Lst, Elem, 1, Nth1),
    % Stop after finding 1 solution
    !.
% Otherwise, succeed with 0
nth1_once_else_0(_Elem, _Lst, 0).

% Using Upto and Nth1 arguments, rather than unnecessary & slow recursion
nth1_once_else_0_([Elem|_], Elem, Nth1, Nth1).
nth1_once_else_0_([_|T], Elem, Upto, Nth1) :-
    % Loop through the list elements
    Upto1 is Upto + 1,
    nth1_once_else_0_(T, Elem, Upto1, Nth1).

swi-prolog 中的结果:

?- nth1_once_else_0(c, [a, b, c, a, b, c], Nth1).
Nth1 = 3.

?- nth1_once_else_0(z, [a, b, c, a, b, c], Nth1).
Nth1 = 0.

?- nth1_once_else_0(Char, [a, b, c, a, b, c], Nth1).
Char = a,
Nth1 = 1.

?- nth1_once_else_0(Char, [a, b, c, a, b, c], 2).
Char = b.

?- nth1_once_else_0(b, [a, b, c, a, b, c], 3).
false.

下面是一个改进的版本:

nth1_once_else_0(Elem, Lst, Nth1) :-
    % Start at element 1
    nth1_once_else_0_(Lst, Elem, 1, Nth1),
    % Stop after finding 1 solution
    !.
% Otherwise, succeed with 0
nth1_once_else_0(_Elem, _Lst, 0).

% Using Upto and Nth1 arguments, rather than unnecessary & slow recursion
nth1_once_else_0_([Elem|_], Elem, Upto, Nth1) :-
    % Found first match on element
    !,
    Upto = Nth1.
nth1_once_else_0_([_|T], Elem, Upto, Nth1) :-
    % Loop through the list elements
    Upto1 is Upto + 1,
    nth1_once_else_0_(T, Elem, Upto1, Nth1).

...以防止超过第一个元素匹配:

?- nth1_once_else_0(c, [a, b, c, a, b, c], 6).
false.

?- nth1_once_else_0(c, [a, b, c, a, b, c], Nth1).
Nth1 = 3.

?- nth1_once_else_0(z, [a, b, c, a, b, c], Nth1).
Nth1 = 0.

【讨论】:

  • 如果您的定义是非关系的,为什么要使用描述性名称?
  • @false 法律在哪里规定非关系定义不能有合理的描述性名称?你建议什么更好的谓词名称?
  • 问题是为什么这个定义完全是非关系的?
  • @false brebs 答案到底有什么问题?这对我来说可以。它只是谓词的名称吗?
  • ?- nth1_once_else_0(c, [a,b,c,a,b, c],Nth1), Nth1 = 6. false.?- Nth1 = 6, nth1_once_else_0(c, [a,b,c,a,b, c],Nth1). 成功了。所以它是非关系的。
【解决方案5】:

我尝试了很长时间,并编写了一些对我有用的代码,我认为将我的代码发布在一个单独的答案中可能会很好。

我提供的代码的最后更新如下:

find(X [], 0).
find(X, [X|_], 1).
find(X, [_|Xs], N) :- find(X, Xs, Rest), N is 1 + Rest.

这适用于像这样的查询 ?- find(a, [a, b, c, d], N) -&gt; yes. N = 1.?- find(c, [a, b, c, d], N) -&gt; yes. N = 1.

但它不适用于以下形式的查询:?- find(d, [a, b, c], N) -&gt; yes. N = 3. 它应该给我以下答案:?- find(d, [a, b, c], N) -&gt; yes. N = 0.

我想通了,问题是当列表被处理掉时,它计数了很长时间,直到列表中没有剩余元素,然后它只是将 0 添加到现有计数器。所以 N 总是给定列表的长度。然后我写了一些规则从头开始“存储”列表,我写了另一个规则来计算列表的长度。现在如果列表被处理掉并且列表中有 0 个元素,它必须将 N 设置为列表的负长度。这样一来,当列表为空时,我们总是得到 N = 0。

我在这里尝试用一个示例查询再次说明问题:?- find(d, [a, b, c], N)

它首先匹配第三行的规则,它必须调用find(d, [b,c], Rest)

N 现在是:1 + 休息。

find(d, [b,c], Rest) 调用之后它调用 find(d, [c], Rest)

N 现在是 1 + (1 + Rest)。

find(d, [c], Rest) 调用之后,它调用“find(d, [], 0)”。

N 现在是 1 + (1 + (1 + Rest))。Rest = 0,所以我们得到:

N = 1 + (1 + (1 + 0)) = 3。

我希望我把问题解释清楚,让每个人都能理解。我是这样解决的:

find(X, [], 0).
find(X, [X|_], 1).

find(X, [], -N, List) :- list_length(List, LENGTH), N is LENGTH, write(LENGTH).
find(X, [X|_], 1, List).
find(X, [Y|Xs], N, List) :- find(X, Xs, Rest, List), N is 1 + Rest.

find(X, [Y|Xs], N) :- find(X, Xs, Rest, [Y|Xs]), N is 1 + Rest.

list_length([], 0).
list_length([X|Xs], LENGTH) :- list_length(Xs, Rest), LENGTH is 1 + Rest.

我基本上和以前一样做,但现在我通过递归带着起始列表。在到达空列表时,我正在计算起始列表的长度,而不是返回 0,而是返回起始列表的负长度。 N 的计算如下所示:

N = 1 + (1 + (1 + (-3))) = 0。

在-3 的位置,旧版本中有一个 0。

我希望我解释得很好。我仍然是一个初学者,我感谢所有能给我提示或建议的人。

编辑:代码更新

我在 cmets 中改进了我的代码,但它仍然有一些我现在解决的问题。我认为这与 cmets 有点混淆,所以我现在正在编辑这个答案以包括我最新的解决方案。

我之前的问题如下:

  • 它不仅找到了第一个索引,还找到了第二个、第三个等等。我用感叹号解决了这个问题。
  • 当给出以下查询时它正在寻找更多答案:?- find(a, [], N). 我通过在第一行添加感叹号解决了这个问题。
  • 查询?- find(a, [a, b, c], 0). 成功了,但它不应该成功,因为 a 在列表中。但是它没有成功查询?- find(a, [b,a,c], 0).
  • 查询成功?- find(a, [a, b, c, a], 4) 但它不应该因为第 4 个 a 不是 a 的第一次出现。查询 ?- find(a, [b,a,c,a], 4) 的代码未成功。

所以我发现问题必须出在第一个元素上。我通过确保 X 不能等于 Y 来修复它。

这是更新后的代码:

find(X, [], 0) :- !.
find(X, [X|_], 1) :- !.

find(X, [], -N, List) :- list_length(List, LENGTH), N is LENGTH, !.
find(X, [X|_], 1, List) :- !.
find(X, [Y|Xs], N, List) :- find(X, Xs, Rest, List), N is 1 + Rest.

find(X, [Y|Xs], N) :- X == Y, find(X, Xs, Rest, [Y|Xs]), N is 1 + Rest.

list_length([], 0).
list_length([X|Xs], LENGTH) :- list_length(Xs, Rest), LENGTH is 1 + Rest.

我知道这可能不是一个好的解决方案,但它满足了我提出的所有要求并且对我有用。如果有人有任何改进的想法或建议,我很高兴听到他们的意见!

【讨论】:

  • 我进一步改进了这段代码,以便它在找到一个解决方案后停止。所以它满足了它应该只找到第一个索引的要求:find(X, [], 0). find(X, [X|_], 1) :- !. find(X, [], -N, List) :- list_length(List, LENGTH), N is LENGTH, write(LENGTH). find(X, [X|_], 1, List) :- !. find(X, [Y|Xs], N, List) :- find(X, Xs, Rest, List), N is 1 + Rest. find(X, [Y|Xs], N) :- find(X, Xs, Rest, [Y|Xs]), N is 1 + Rest. list_length([], 0). list_length([X|Xs], LENGTH) :- list_length(Xs, Rest), LENGTH is 1 + Rest.我只是添加了感叹号。
  • 如果您自己编写的答案完全解决了您的问题并且对此感到满意,我认为没有理由将已接受的答案更改为另一个答案。
  • ?- find(c,[X],0),X=c. X = c.?- X=c,find(c,[X],0). false.
  • 要么都成功,要么都失败。
  • 是的,合取应该是可交换的(模非终止和错误)。否则,只能在纯粹的程序层面上理解程序。
猜你喜欢
  • 2013-01-13
  • 1970-01-01
  • 2015-12-16
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-08
相关资源
最近更新 更多