【问题标题】:Splitting a list before and after a particular element in prolog (without using "split" predicate?)在序言中的特定元素之前和之后拆分列表(不使用“拆分”谓词?)
【发布时间】:2017-11-28 09:43:51
【问题描述】:

我正在尝试将列表拆分为特定元素之前的项目(特别是“停止”一词)以及该元素之后的项目。我知道您可以使用 split 来执行此操作,但是我是 prolog 的新手,因此我目前正在尝试在不使用这些功能的情况下进行操作,因此我真的很想知道这是否可能? (也许还有一些正确方向的指针)

即与列表;

L = [tea,coffee,sugar,cake,stop,meat,fish,eggs,flour]

理想情况下,我希望在“停止”时拆分列表,让我留下,

L2 = [tea, coffee, sugar, cake] // and
L3 = [meat, fish, eggs, flour]

【问题讨论】:

  • 是的,有可能。
  • @WillemVanOnsem 你能指出我应该做的正确方向吗?目前,我只能浏览一个列表并计算出发生了“停止”,但我不知道如何提取两边的元素列表?
  • 通常你在 Prolog 中以声明和递归的方式声明谓词。因此,也许您应该考虑“如果列表为空,该怎么办?” “鉴于我们已经拆分了列表的尾部,我们应该如何拆分列表”等。
  • @WillemVanOnsem 我知道如何递归(如果它是空的,我只想对它不做任何事情),并且可以识别元素是否是“停止”。我是否将“停止”之前的元素附加到新列表中?递归([])。递归([H|T]):- 附加(H,_,L),\+check_stop(H),递归(T)。
  • 你的意思是 append/3 表示“拆分谓词”吗?

标签: list split prolog


【解决方案1】:

使用seq//1

list_splitonstop(Xs, As, Bs) :-
   phrase( ( seq(As), [stop], seq(Bs) ), Xs).

此版本按您的预期工作:

?- L = [tea,coffee,sugar,cake,stop,meat,fish,eggs,flour],
       list_splitonstop(L, L1, L2).
   L = [tea, coffee, sugar, cake, stop, meat, fish, eggs, flour],
   L1 = [tea, coffee, sugar, cake],
   L2 = [meat, fish, eggs, flour]
;  false.

但是,这真的是最好的解决方案吗?最后的; false 可能表明它不是。但我们不能肯定地说。我们必须找出另一种情况,该解决方案无法按预期工作。您在其他编程语言中也面临着类似的问题,必须非常依赖程序员的想象力才能找出边界情况等。

幸运的是,我们在这里使用 Prolog,它可以帮助我们理解我们实际定义的内容。

一个非常简单的开始是询问最一般的查询。就这样:

| ?- list_splitonstop(L, L1, L2).
   L = [stop], L1 = [], L2 = []
;  L = [stop,_A], L1 = [], L2 = [_A]
;  L = [stop,_A,_B], L1 = [], L2 = [_A,_B]
;  L = [stop,_A,_B,_C], L1 = [], L2 = [_A,_B,_C]
...

看看每个答案!我们以第三个为例。 L = [stop,_A,_B] 表示此答案包括具有三个元素的 all 列表,其中第一个元素是 stop。因此,我们在这里寻找无限 的解决方案,这些解决方案都用几个字符进行了紧凑的描述!甚至bzip2 -99 都做不到!

这些是仅有的三个元素的列表吗?我们不能仅从这个单一的查询中说出这一点,因为 Prolog 可能会以 不公平 的方式列举答案。想象一下,你让某人告诉你所有的自然数,但那个人从 0、2、4 开始……显然,这种枚举对奇数是非常不公平的。同样,可能缺少一些答案...

在 Prolog 中,我们可以坚持只查看长度为 3 的列表:

| ?- L = [_,_,_], list_splitonstop(L, L1, L2).
   L = [stop,_A,_B], L1 = [], L2 = [_A,_B]
;  L = [_A,stop,_B], L1 = [_A], L2 = [_B]
;  L = [_A,_B,stop], L1 = [_A,_B], L2 = []
;  false.

所以我们可以在一个查询中查询所有长度为 3 的相关案例。请注意,这些 _A_B 变量代表任何术语!请花点时间欣赏您正在查看的内容:长度为 3 的列表的所有案例。没有其他案例需要考虑!

当您查看此类答案时,可能会出现一些问题。比如:这三个答案是重叠的,还是真的不相交? Prolog 知道答案。只需重复实际目标并计算得到的答案:

| ?- L = [_,_,_], list_splitonstop(L, L1, L2), list_splitonstop(L, L1, L2).
(answers same as above)

所以我们得到完全相同的答案。没有固有的冗余。

另一个问题可能是:L 是否总是恰好有一个可能的拆分? (换句话说:是否存在函数依赖?)

我们可以通过询问具有不同 L1L2L 来解决此问题:

| ?- L = [_,_,_], dif(L1-L2,L1x-L2x),
     list_splitonstop(L, L1, L2), list_splitonstop(L, L1x, L2x).
   L = [stop,stop,_A], L1 = [], L2 = [stop,_A], L1x = [stop], L2x = [_A]
;  L = [stop,_A,stop], L1 = [], L2 = [_A,stop], L1x = [stop,_A], L2x = []
;  L = [stop,stop,_A], L1 = [stop], L2 = [_A], L1x = [], L2x = [stop,_A]
;  L = [_A,stop,stop], L1 = [_A], L2 = [stop], L1x = [_A,stop], L2x = []
;  L = [stop,_A,stop], L1 = [stop,_A], L2 = [], L1x = [], L2x = [_A,stop]
;  L = [_A,stop,stop], L1 = [_A,stop], L2 = [], L1x = [_A], L2x = [stop]
;  false.

那么,我现在可能会问你:你想要上述情况吗?如果stop 出现多次?显然,您没有具体说明这一点,我们需要您提供更多信息。 Prolog 至少有助于识别此类情况。


如何识别多余的答案。

在上述案例中,我们观察到没有多余的答案。但是当他们出现时,他们是如何出现的呢?这是一个这样的例子:member/2 是内置的,会产生(有时)冗余答案,memberd/2 没有这种冗余。实际问题是:

e 作为元素/成员的二元素列表看起来如何?

?- Xs = [_,_], member(e, Xs).
   Xs = [e, _A]
;  Xs = [_A, e].

?- Xs = [_,_], member(e, Xs), member(e, Xs).
   Xs = [e, _A]
;  Xs = [e, e]                %  <--- redundant
;  Xs = [e, e]                %  <--- redundant
;  Xs = [_A, e].

?- Xs = [_,_], memberd(e, Xs).
   Xs = [e, _A]
;  Xs = [_A, e], dif(_A, e)
;  false.

?- Xs = [_,_], memberd(e, Xs), memberd(e, Xs).
   Xs = [e, _A]
;  Xs = [_A, e], dif(_A, e)
;  false.

如果您只想查看那些允许冗余的答案,您可以改为:

?- Xs = [_,_], member(e, Xs), \+ \+ call_nth(member(e, Xs), 2).
   Xs = [e, _A]
;  Xs = [_A, e].

换句话说,member/2所有答案都允许这种冗余。请注意,member/2 并不总是容易出现冗余。特别是,如果列表包含不同的(不可统一的)元素,则根本没有冗余。这是一个常见的用例。

?- Xs = [a,b], member(X, Xs), \+ \+call_nth(member(X, Xs),2).
false.

事实上,在这种情况下,也就是在查询X 时,member/2 可能比memberd/2 更有效。

【讨论】:

    【解决方案2】:

    正如评论中所说,递归定义是最自然的方法:

    split_on_stop([], [[]]).
    split_on_stop([stop|T], [[]|T2]) :-
        split_on_stop(T, T2).
    split_on_stop([H|T], [[H|T2]|T3]) :-
        dif(H, stop),
        split_on_stop(T, [T2|T3]).
    

    此实现适用于stop 的多次出现:第二个参数是这些拆分列表的列表。

    这里要弄清楚的复杂部分是所有三种情况下第二个参数的内容(当第一个参数为空时,当我们找到一个停止时,以及当我们找到一个不是停止的东西时)。

    注意

    如果保证输入列表只有一站,则可以大大缩短此实现(但它会丢失很多可取的行为):

    split_on_stop([stop|T], [], T).
    split_on_stop([H|T], [H|T2], T3) :-
        dif(H, stop),
        split_on_stop(T, T2, T3).
    

    【讨论】:

      【解决方案3】:

      append/3是你的法宝:

      split_on_stop(List, Before, After) :-
          append(Before, [stop|After], List).
      

      不多也不少,所以在大多数情况下,您只需内联附加目标。

      【讨论】:

      • 当然,这假设这是一个完整的解决方案,列表中只有一个stop
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-19
      • 2010-10-09
      • 2021-11-21
      • 1970-01-01
      • 1970-01-01
      • 2020-11-01
      相关资源
      最近更新 更多