Prolog 将电影放在dvd上,但有风格/代码问题

Prolog 将电影放在dvd上,但有风格/代码问题,prolog,knapsack-problem,Prolog,Knapsack Problem,我制作了一个prolog程序,向我展示在dvd上安装东西的最佳方式。问题在代码注释中,我将在下面粘贴以供参考,但归结起来是: 是否有一种反向切割操作符使其搜索更多,尽管它已经匹配?参见fitexact,类似fitexact(大小,总和,L,L):-Sum 跟踪已处理电影的最佳方法是什么?我收回它们,但不知道没有它怎么做 fitfuzzy使用如果则构造。我不知道该怎么看他们,在序言中感觉很奇怪。然而,试图让它递归让我感到非常困惑:) %给你一张电影和尺寸的清单,试着把它们都放在dvd上 %尽可能少

我制作了一个prolog程序,向我展示在dvd上安装东西的最佳方式。问题在代码注释中,我将在下面粘贴以供参考,但归结起来是:

  • 是否有一种反向切割操作符使其搜索更多,尽管它已经匹配?参见fitexact,类似fitexact(大小,总和,L,L):-Sum
  • 跟踪已处理电影的最佳方法是什么?我收回它们,但不知道没有它怎么做

  • fitfuzzy使用
    如果
    构造。我不知道该怎么看他们,在序言中感觉很奇怪。然而,试图让它递归让我感到非常困惑:)

  • %给你一张电影和尺寸的清单,试着把它们都放在dvd上 %尽可能少地浪费空间。 %设置dvd大小 dvdsize(4812)。 %数据库中所有电影的总和 电影大小(大小):-findall(S,电影(,S),LS),sum_列表(LS,size)。 %数据库中所有电影的计数 电影计数(计数):-findall(S,电影(S,LS),LS),长度(LS,计数)。 %看看哪个正好合适 %这就是我遇到麻烦的地方,最初的想法是 %这就像下面的模糊搜索,但我不明白我怎么能做到 %表达“当没有更适合的电影时, %总和比DVD的大小小,列表也可以。 fitexact(电影):-DVD大小(大小),fitexact(大小,0,[],电影)。 %完美合身时停止 %所以在这里我尝试了大小、总和和总和删除DVD(DVD) ; NewWasted等于Wasted+1, write('fitfulze:将浪费的空间增加到')、write(NewWasted)、nl、, fitfuzzy(DVD、尺寸、新尺寸) ). 地位:- 电影计数(电影左), 电影大小(电影大小), write(‘电影左:’),write(电影左),nl, 写入('Size left:')、写入(MoviesSize)、nl。 burnloop:- 电影计数(C),C>0, 菲茨(DVD), 地位 写入('DVD=')、打印(DVD)、nl、nl、, 伯恩洛普。 %movies.db包含电影列表(名称、大小)。声明。它也 %必须具备:-动态(电影/2)。在顶部,以便缩回工作。 去:- ['movies.db'], 伯恩洛普。
  • 如果您希望在提供每个解决方案后,事情的表现都像您一直在要求另一个解决方案一样,但将它们全部收集到一个列表中,
    findall
    就是您想要的

  • 如果这一切都发生在一个查询中,您可以传递一个使用过的电影列表。例如,burn loop会将迄今为止使用的电影列表作为参数;Fitfuzz会接受这个列表&为添加的DVD填写一个新版本的电影,然后将新列表传递给burnloop。或者,由于DVD中有新电影,请编写一个新谓词,将DVD中的电影添加到旧列表中,以生成新电影

  • 如果您让fitexact按照当前的方式继续,但同时保留最接近DVD大小的电影列表,这样,当没有完全填满DVD时,它不会失败,而是会生成该列表


  • 只是一个一般性的评论:与跟踪处理过的电影相比,我发现首先获取(例如,通过findall/3)仍需要处理的电影列表,然后简单地将该列表处理掉更为自然。因此,您有了
    burn_dvd(List0,dvd,List)
    ,它将电影列表(可能与它们的大小结合在一起,比如以
    movie_size(Name,size)
    的形式)作为第一个参数,构造一张dvd(通过从列表0中选择尽可能多的电影放在一张dvd上,例如在按大小排序列表之后),第三个参数是剩余电影的列表。然后,您就有了一个自然扩展burn_DVD(列表,DVD),它可以简单地构造DVD,直到不再有电影保留:

    burn_dvds([], []) :- !.
    burn_dvds(Movies0, [DVD|DVDs]) :-
        burn_dvd(Movies0, DVD, Movies),
        burn_dvds(Movies, DVDs).
    
    不需要断言/1或收回/1。如果burn_dvd/3非确定性地构造一张dvd,则可能有多种解决方案,这是您可能想要的,也是很自然的

    但是,使用if-then-else是完全可以的,可以通过模式匹配来表示的所有内容都应该通过模式匹配来表示,因为它通常会生成更通用、更高效的代码

    format/2也可以帮助您进行输出:而不是:

    write('Movies left: '), write(MoviesLeft), nl
    
    你可以写:

    format("Movies left: ~w\n", [MoviesLeft])
    

    通常,很少需要手动输出,因为您可以随时让顶级打印解决方案。在我们的例子中,当您查询时,burn_DVD/2自然会发出DVD列表作为其答案。

    Prolog的“最佳实践规则”指出,除非绝对需要断言/收回(即没有声明性方法),否则应避免断言/收回

    这里有一个使用select/3生成所有组合的程序

    movie(a, 10).
    movie(b, 3).
    movie(c, 5).
    movie(d, 6).
    movie(e, 10).
    
    dvdsize(20).
    
    burn(Best) :-
        findall(N-S, movie(N,S), L),
        dvdsize(Max),
        setof(Wasted-Sequence, fitmax(L, Max, Wasted, Sequence), All),
        All = [Best|_],
        maplist(writeln, All).
    
    fitmax(L, AvailableRoom, WastedSpace, [Title|Others]) :-
        select(Title - MovieSize, L, R),
        MovieSize =< AvailableRoom,
        RoomAfterMovie is AvailableRoom - MovieSize,
        fitmax(R, RoomAfterMovie, WastedSpace, Others).
    
    fitmax(_, WastedSpace, WastedSpace, []).
    

    我以前的回答是“快而脏”,随着电影数量的增加,很快就显示出它的局限性。这里有一个更好的方法来找到最合适的答案,并与之前的答案(根据测试要求重新制定)进行比较

    优化的关键是标签背包,Axel在发布问题时正确使用了标签背包。我在CLP(FD)support中搜索了一种解决此问题的适当方法,如下所示:

    :- [library(clpfd)].
    
    %%  use CLP(FD) to find best fit
    %
    burn_knapsack(Best, Wasted) :-
        dvdsize(Max),
        findall(Title - Size, movie(Title, Size), Movies),
        knaps(Movies, Max, Best, Wasted).
    
    knaps(Movies, Max, Best, Wasted) :-
        findall([Flag, Title, Size],
            (Flag in 0..1, member(Title - Size, Movies)), AllMovies),
        transpose(AllMovies, [ToBurn, Titles, Sizes]),
    
        Actual #=< Max,
        scalar_product(Sizes, ToBurn, #=, Actual),
        labeling([max(Actual)], [Actual|ToBurn]),
        findall(Title, (nth1(I, ToBurn, 1),
                nth1(I, Titles, Title)), Best),
        Wasted is Max - Actual.
    
    %%  compute all combinations of movies that fit on a dvd
    %   it's a poor man clpfd:scalar_product/4
    %
    burn_naive(Best, Wasted) :-
        dvdsize(Max),
        findall(Title - Size, movie(Title, Size), Movies),
        naive(Movies, Max, Best, Wasted).
    
    naive(Movies, Max, Best, Wasted) :-
        setof(Wasted-Sequence, fitmax(Movies, Max, Wasted, Sequence), [Wasted-Best|_]).
    
    fitmax(L, AvailableRoom, WastedSpace, [Title|Others]) :-
        select(Title - MovieSize, L, R),
        MovieSize =< AvailableRoom,
        RoomAfterMovie is AvailableRoom - MovieSize,
        fitmax(R, RoomAfterMovie, WastedSpace, Others).
    fitmax(_, WastedSpace, WastedSpace, []).
    
    %%  run test with random generated list
    %
    %   From,To are num.of.movies
    %   SzMin, SzMax min/max+1 of each movie size
    %
    test_performance(From, To, DvdSize, SzMin, SzMax) :-
        forall((between(From, To, NumMovies),
            gen_movies(NumMovies, SzMin, SzMax, Movies)
               ),
               (   (   NumMovies < 11
               ->  test(naive, Movies, DvdSize)
               ;   true
               ),
               test(knaps, Movies, DvdSize)
               )).
    test(Method, Movies, DvdSize) :-
        time(once(call(Method, Movies, DvdSize, Best, Wasted))),
        writeln((Method, Best, Wasted)).
    
    gen_movies(NumMovies, SzMin, SzMax, Movies) :-
        findall(Title-Size,
            (   between(1, NumMovies, Title),
                random(SzMin, SzMax, Size)
            ), Movies).
    

    好的,我应该列一个清单,谢谢你指出这一点。关于多个解决方案,您是指已创建dvd的排列吗?如果可能的话,我想避免这种情况,这就是我收回它们的原因。不仅是排列,而且显然有很多方法可以制作出单张DVD,但仍然会导致非同构的解决方案。例如,当两部电影大小完全相同时,您可以将其中一部或另一部放在两张仍有足够空间的DVD上。使用诸如assert/1和retract/1之类的副作用会妨碍良好的声明性解决方案,使您可以通过使用Prolog的自然搜索策略轻松探索这些不同的变体。是的,我想我在同一个函子中使用retract时注意到了这些副作用,请参见注释
    ?- burn(X).
    0-[a,e]
    0-[e,a]
    1-[a,b,d]
    1-[a,d,b]
    1-[b,a,d]
    1-[b,d,a]
    1-[b,d,e]
    1-[b,e,d]
    1-[d,a,b]
    1-[d,b,a]
    1-[d,b,e]
    1-[d,e,b]
    1-[e,b,d]
    1-[e,d,b]
    2-[a,b,c]
    2-[a,c,b]
    2-[b,a,c]
    2-[b,c,a]
    2-[b,c,e]
    2-[b,e,c]
    2-[c,a,b]
    2-[c,b,a]
    2-[c,b,e]
    2-[c,e,b]
    2-[e,b,c]
    2-[e,c,b]
    4-[a,d]
    4-[d,a]
    4-[d,e]
    4-[e,d]
    5-[a,c]
    5-[c,a]
    5-[c,e]
    5-[e,c]
    6-[b,c,d]
    6-[b,d,c]
    6-[c,b,d]
    6-[c,d,b]
    6-[d,b,c]
    6-[d,c,b]
    7-[a,b]
    7-[b,a]
    7-[b,e]
    7-[e,b]
    9-[c,d]
    9-[d,c]
    10-[a]
    10-[e]
    11-[b,d]
    11-[d,b]
    12-[b,c]
    12-[c,b]
    14-[d]
    15-[c]
    17-[b]
    20-[]
    X = 0-[a, e].
    
    :- [library(clpfd)].
    
    %%  use CLP(FD) to find best fit
    %
    burn_knapsack(Best, Wasted) :-
        dvdsize(Max),
        findall(Title - Size, movie(Title, Size), Movies),
        knaps(Movies, Max, Best, Wasted).
    
    knaps(Movies, Max, Best, Wasted) :-
        findall([Flag, Title, Size],
            (Flag in 0..1, member(Title - Size, Movies)), AllMovies),
        transpose(AllMovies, [ToBurn, Titles, Sizes]),
    
        Actual #=< Max,
        scalar_product(Sizes, ToBurn, #=, Actual),
        labeling([max(Actual)], [Actual|ToBurn]),
        findall(Title, (nth1(I, ToBurn, 1),
                nth1(I, Titles, Title)), Best),
        Wasted is Max - Actual.
    
    %%  compute all combinations of movies that fit on a dvd
    %   it's a poor man clpfd:scalar_product/4
    %
    burn_naive(Best, Wasted) :-
        dvdsize(Max),
        findall(Title - Size, movie(Title, Size), Movies),
        naive(Movies, Max, Best, Wasted).
    
    naive(Movies, Max, Best, Wasted) :-
        setof(Wasted-Sequence, fitmax(Movies, Max, Wasted, Sequence), [Wasted-Best|_]).
    
    fitmax(L, AvailableRoom, WastedSpace, [Title|Others]) :-
        select(Title - MovieSize, L, R),
        MovieSize =< AvailableRoom,
        RoomAfterMovie is AvailableRoom - MovieSize,
        fitmax(R, RoomAfterMovie, WastedSpace, Others).
    fitmax(_, WastedSpace, WastedSpace, []).
    
    %%  run test with random generated list
    %
    %   From,To are num.of.movies
    %   SzMin, SzMax min/max+1 of each movie size
    %
    test_performance(From, To, DvdSize, SzMin, SzMax) :-
        forall((between(From, To, NumMovies),
            gen_movies(NumMovies, SzMin, SzMax, Movies)
               ),
               (   (   NumMovies < 11
               ->  test(naive, Movies, DvdSize)
               ;   true
               ),
               test(knaps, Movies, DvdSize)
               )).
    test(Method, Movies, DvdSize) :-
        time(once(call(Method, Movies, DvdSize, Best, Wasted))),
        writeln((Method, Best, Wasted)).
    
    gen_movies(NumMovies, SzMin, SzMax, Movies) :-
        findall(Title-Size,
            (   between(1, NumMovies, Title),
                random(SzMin, SzMax, Size)
            ), Movies).
    
    ?- test_performance(8,20, 30, 3,7).
    % 93,155 inferences, 0,140 CPU in 0,140 seconds (100% CPU, 665697 Lips)
    naive,[1,2,3,5,6],0
    % 235,027 inferences, 0,159 CPU in 0,159 seconds (100% CPU, 1481504 Lips)
    knaps,[2,3,5,6,8],0
    % 521,369 inferences, 0,782 CPU in 0,783 seconds (100% CPU, 666694 Lips)
    naive,[1,2,3,4,5,6],0
    % 163,858 inferences, 0,130 CPU in 0,131 seconds (100% CPU, 1255878 Lips)
    knaps,[3,4,5,6,7,9],0
    % 1,607,675 inferences, 2,338 CPU in 2,341 seconds (100% CPU, 687669 Lips)
    naive,[1,2,3,4,7,8],0
    % 184,056 inferences, 0,179 CPU in 0,180 seconds (100% CPU, 1027411 Lips)
    knaps,[5,6,7,8,9,10],0
    % 227,510 inferences, 0,156 CPU in 0,156 seconds (100% CPU, 1462548 Lips)
    knaps,[5,6,8,9,10,11],0
    % 224,621 inferences, 0,155 CPU in 0,155 seconds (100% CPU, 1451470 Lips)
    knaps,[6,7,8,9,10,11,12],0
    % 227,591 inferences, 0,159 CPU in 0,159 seconds (100% CPU, 1434836 Lips)
    knaps,[5,7,9,10,11,12,13],0
    % 389,764 inferences, 0,263 CPU in 0,263 seconds (100% CPU, 1482017 Lips)
    knaps,[5,8,9,10,12,13,14],0
    % 285,944 inferences, 0,197 CPU in 0,198 seconds (100% CPU, 1448888 Lips)
    knaps,[8,9,10,12,13,14,15],0
    % 312,936 inferences, 0,217 CPU in 0,217 seconds (100% CPU, 1443891 Lips)
    knaps,[10,11,12,14,15,16],0
    % 343,612 inferences, 0,238 CPU in 0,238 seconds (100% CPU, 1445670 Lips)
    knaps,[12,13,14,15,16,17],0
    % 403,782 inferences, 0,277 CPU in 0,278 seconds (100% CPU, 1456345 Lips)
    knaps,[11,12,13,15,16,17],0
    % 433,078 inferences, 0,298 CPU in 0,298 seconds (100% CPU, 1455607 Lips)
    knaps,[14,15,16,17,18,19],0
    % 473,792 inferences, 0,326 CPU in 0,327 seconds (100% CPU, 1451672 Lips)
    knaps,[14,15,16,17,18,19,20],0
    true.