【问题标题】:Creating a queue structure in Prolog在 Prolog 中创建队列结构
【发布时间】:2020-02-29 18:42:23
【问题描述】:

我正在学习 Prolog,但在学习面向对象背景的语言时遇到了困难。

我正在尝试完成以下说明:

实施分阶段队列。这是一个由两个列表组成的结构: 前端后端,表示为queue(Front, Back)。这 队列是空的iff 两个列表都是空的。如果元素被添加到 队列它们被添加到后端;如果他们被删除,他们是 从前端移除;如果前端变空(并且 后端不为空)然后后端成为带有[]的新前端 作为新的后端。例如,我们可以从一个队列开始 queue([2,5,7], []),加8给我们queue([2,5,7], [8]),去掉2 元素给出queue([7], [8]),添加9 给出queue([7], [9,8]),并且 删除一个元素会得到queue([9,8], [])

我不明白我是如何在.pl 文件中创建并引用队列结构,以使其他谓词可以随后操作和转换

我已经粗略地勾勒出我认为我应该做的事情,既定义了队列结构,也只是一个列表列表。

add_to_q(X, [[H|T]|[H2|T2]], [[H|T]|[X|[H2|T2]]).

queue(X, Y)
add_to_q(A, queue(X,Y), queue(X, [A|Y]). % gives Syntax error: Operator expected

------------------

remove_from_q( [[H | [T|T3]] | [H2|T2]], [[T|T3]] | [H2|T2]]).

queue(X, Y)
remove_from_q( queue(X,[H|T]), queue(H,T).

如何在 Prolog 中定义和使用结构,如何添加 OO 语言中的内容方法,例如 getHeadgetTail,我已经看到了如何您只使用列表来执行此操作,但我不使用列表列表,而是使用两个单独列表的“队列”?

感觉失落!

【问题讨论】:

  • [9,8] 从后到前 没有被反转 的例子似乎并没有使这成为一个队列。
  • @Enigmativity 这就是为什么我称它为funny queue
  • queue([7], [9,8]) 中删除一个元素应该给queue([8,9], []),以保留FIFO 属性。他们忘了扭转它。

标签: data-structures prolog queue structure custom-data-type


【解决方案1】:

我无法处理来自 OO 背景的语言。

帮自己一个忙,在学习 Prolog 的时候忘记你对 OO 的了解,这只会让你在学习 Prolog 时更加困惑。换句话说,不要认为 OO 概念那么我如何将其翻译为 Prolog。将syntactic unification 视为如何构造越来越复杂的谓词的基础。


我不明白我是如何在 PL 文件中创建并引用队列结构的,这样其他谓词可以随后进行操作和转换。

说明为您提供数据结构的基础,即

queue(Front,Back)

和 Front 和 Back 是一个列表。列表示例

[]
[a]
[a,b]
[a|b]

引用队列很容易。由于 Prolog 使用句法统一,统一的一侧是您想要统一的原子,例如queue(Front,Back) 和统一的另一面是 queue(Front,Back) 的转换,您可以在所写的谓词中使用它们。

你已经证明了这一点

add_to_q(A,queue(X,Y),queue(X,[A|Y])

请注意,它缺少结尾 )


add_to_q(A,queue(X,Y),queue(X,[A|Y]). % gives Syntax error: Operator expected

缺少结尾)

add_to_q(A,queue(X,Y),queue(X,[A|Y])).

由于说明给出的示例非常有限,因此很难创建许多测试用例来确保代码可以在真实的生产系统中运行。

这是基于问题的工作代码。

add(Item,queue(Front,Back),queue(Front,[Item|Back])).

remove(Item,queue([Item|[]],Back),queue(Back,[])).
remove(Item,queue([Item|Front],Back),queue(Front,Back)).

:- begin_tests(funny_queue).

funny_queue_test_case_generator(add   , 8    ,queue([2,5,7]  ,[   ] ) ,queue([2,5,7],[8]   ) ).
funny_queue_test_case_generator(remove, 2    ,queue([2,5,7]  ,[  8] ) ,queue([5,7]  ,[8]   ) ).
funny_queue_test_case_generator(remove, 5    ,queue([5,7]    ,[  8] ) ,queue([7]    ,[8]   ) ).
funny_queue_test_case_generator(add   , 9    ,queue([7]      ,[  8] ) ,queue([7]    ,[9,8] ) ).
funny_queue_test_case_generator(remove, 7    ,queue([7]      ,[9,8] ) ,queue([9,8]  ,[]    ) ).

test(add,[forall(funny_queue_test_case_generator(add,Item,Funny_queue_0,Funny_queue))]) :-
    add(Item,Funny_queue_0,Funny_queue_expected),

    assertion( Funny_queue == Funny_queue_expected ).

test(add,[nondet,forall(funny_queue_test_case_generator(remove,Item,Funny_queue_0,Funny_queue))]) :-
    remove(Item,Funny_queue_0,Funny_queue_expected),

    assertion( Funny_queue == Funny_queue_expected ).

:- end_tests(funny_queue).

【讨论】:

  • "在学习 Prolog 时,帮自己一个忙,忘记你对 OO 的了解,学习 Prolog 只会让你更加困惑。"也就是说,直到您遇到作为对象的 Prolog 模块(尽管不乏喜欢假装的 Prolog 实践者)。
【解决方案2】:

这是一个有趣的队列。当队列的后面移动到前面时,项目会以相反的顺序弹出。这不是真正的队列。

不过,操作相当简单。

首先,我们将队列表示为复合术语queue(X,Y),其中XY 是表示队列前面和后面的列表。

我们需要一种方法来获取一个空队列:

empty(queue([],[])).

要将项目添加到队列中,我们这样做:

add(A,queue(X,Y),queue(X,[A|Y])).

现在,说明不一致。他们说两个空列表代表一个空队列,并且总是将新项目添加到后列表中。那么,如果第一个列表开始为空并且从未添加到其中,那么它如何永远变为空

所以我们必须提供一个谓词,允许我们从一个空的前面列表中删除,只要后面不为空。

remove(queue([],[A|X]),A,queue(X,[])).

这会将后列表的尾部移到前面,并将尾部的第一个元素返回给A

最后,如果前面的列表不为空,那么我们这样做:

remove(queue([A|T],X),A,queue(T,X)).

让我们测试这 4 个谓词。

?-
    empty(Q),
    add(7,Q,Q2),
    add(5,Q2,Q3),
    add(2,Q3,Q4),
    write(Q4),nl,
    remove(Q4,A,Q5),
    add(3,Q5,Q6),
    remove(Q6,B,Q7),
    remove(Q7,C,Q8),
    remove(Q8,D,Q9),
    empty(Q9),
    write([A,B,C,D]).

Q9 必须在将 4 个原子添加到队列然后删除它们后为空。 [A,B,C,D] 统一为[2,5,7,3]

Q4 是说明中描述的起始状态,尽管它是 queue([],[2,5,7]),因为我们被指示始终添加到后备列表中。

所以这一切都按照我们的指示行事。

但这不是真正的队列!

如果这是一个真正的队列,那么添加的第一个项目应该是删除的第一个项目。预期输出为[7,5,2,3]。为了实现这一点,我们将重写第一个 remove 谓词,如下所示:

remove(queue([],Z),A,queue(X,[])) :- reverse(Z,[A|X]).
reverse(X,Y):- reverse(X,[],Y).
reverse([],A,A).
reverse([H|T],A,R) :- reverse(T,[H|A],R). 

运行带有该更改的代码会给我们一个队列的预期输出。

【讨论】:

    【解决方案3】:

    Guy Coder 说了什么。忘记 OO 任何事情。都是关于模式匹配的。

    这就是你所需要的:

    • 首先,一种创建空队列的方法:
    empty( q([],[]) ).
    
    • 一旦你有了它,排队的东西是微不足道的。 只需将入队的项目添加到“后退”队列即可:

      enque( X , q(F,B) , q(F, [X|B] ) ) .
      

      解释: prolog 中的列表是递归数据结构。按照惯例,一个空列表是 表示为原子[](将其视为字符串)。写入非空列表 使用方括号表示法([1][1,2] 等)。但是...这只是句法 数据结构的糖./2:

      • [1].( 1 , [] ) 的简写
      • [1,2].( 1 , .( 2 , [] ) ) 的简写
      • ...等等。


      您可以欣赏方括号表示法的实用性。如果你看 这个递归数据结构,你会注意到一个列表由一个项目组成, 后跟另一个 [嵌套] 列表。该单个项目是列表的 head,并且 嵌套列表是它的tail

      注意: 递归是prolog中的一个重要概念。学习思考 关于递归方面的事情会对你有很大帮助。

      符号[H|T] 是一种将列表分解为headtail 的方法。 这只是.(H,T) 的语法糖。这意味着它也可以使用 通过组合创建列表。

      所以我们的enqueue/3 谓词使用它来构造带有X 的新“后退”列表 放在前面。

    • 最后,出队并不复杂:

      deque( q( []     , Bs ) , F , q( Fs , [] ) ) :- reverse( Bs, [F|Fs] ).
      deque( q( [F|Fs] , Bs ) , F , q( Fs , Bs ) ) .
      

      说明:

      谓词中的第一项处理“前”列表时出队的情况 是空的。我们只是反转后面的列表,获取它的第一个元素,head (F) 作为 出队的项目,并将其 tail (Fs) 作为新的“前”列表。我们给新的 排队一个空的“后退”列表。

      谓词中的第二项处理出队的情况,当 “front”列表为空。更简单:我们只抓住“前”的head list,将其统一为出队项,并使用队列的tail构造新队列 旧的“前”列表作为新的“前”列表,“后”列表保持原样。

    示例

    https://swish.swi-prolog.org/p/phased-queue.pl 运行它

    
    empty( q([],[]) ).
    
    enque( X , q(F,B) , q(F, [X|B] ) ).
    
    deque( q( []     , Bs ) , F , q(Fs,[]) ) :- reverse( Bs, [F|Fs] ).
    deque( q( [F|Fs] , Bs ) , F , q(Fs,Bs) ).
    
    
    run_example :-
      empty( Q0             ) , writeln( queue :         Q0  ) ,
      enque( 1   , Q0 , Q1  ) , writeln( enque :         Q1  ) ,
      enque( 2   , Q1 , Q2  ) , writeln( enque :         Q2  ) ,
      enque( 3   , Q2 , Q3  ) , writeln( enque :         Q3  ) ,
      enque( 4   , Q3 , Q4  ) , writeln( enque :         Q4  ) ,
      deque( Q4  , X1 , Q5  ) , writeln( deque : x(X1) : Q5  ) ,
      enque( 5   , Q5 , Q6  ) , writeln( enque :         Q6  ) ,
      enque( 6   , Q6 , Q7  ) , writeln( enque :         Q7  ) ,
      deque( Q7  , X2 , Q8  ) , writeln( deque : x(X2) : Q8  ) ,
      deque( Q7  , X3 , Q9  ) , writeln( deque : x(X3) : Q9  ) ,
      deque( Q9  , X4 , Q10 ) , writeln( deque : x(X4) : Q10 ) ,
      deque( Q10 , X5 , Q11 ) , writeln( deque : x(X5) : Q11 ) ,
      deque( Q11 , X6 , Q12 ) , writeln( deque : x(X6) : Q12 ) ,
      deque( Q12 , X7 , Q13 ) , writeln( deque : x(X7) : Q13 ) ,
      empty( Q13            )
      .
    
    

    【讨论】:

    • 你可能会喜欢这个。 Difference List。您在回答中给了我参考,但遗憾的是没有投票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多