Пазл о переходе моста с clpfd

Я попытался решить проблему «Побег из Зурга» с помощью clpfd. https://web.engr.oregonstate.edu/~erwig/papers/Zurg_JFP04.pdf Игрушки начинаются слева и идут направо. Вот что у меня есть:

:-use_module(library(clpfd)).

toy(buzz,5).
toy(woody,10).
toy(res,20).
toy(hamm,25).

%two toys cross, the time is the max of the two.
cross([A,B],Time):-
  toy(A,T1),
  toy(B,T2),
  dif(A,B),
  Time#=max(T1,T2).
%one toy crosses
cross(A,T):-
  toy(A,T).

%Two toys travel left to right
solve_L(Left,Right,[l_r(A,B,T)|Moves]):-
  select(A,Left,L1),
  select(B,L1,Left2),
  cross([A,B],T),
  solve_R(Left2,[A,B|Right],Moves).

%One toy has to return with the flash light
solve_R([],_,[]).
solve_R(Left,Right,[r_l(A,empty,T)|Moves]):-
  select(A,Right,Right1),
  cross(A,T),
  solve_L([A|Left],Right1,Moves).

solve(Moves,Time):-
   findall(Toy,toy(Toy,_),Toys),
   solve_L(Toys,_,Moves),
   all_times(Moves,Times),
   sum(Times,#=,Time).

all_times([],[]).
all_times(Moves,[Time|Times]):-
  Moves=[H|Tail],
  H=..[_,_,_,Time],
  all_times(Tail,Times).

Запрашивая ?-solve(M,T) или ?-solve(Moves,T), labeling([min(T)],[T])., я получаю решение, но не одно = ‹60. (Я тоже не вижу его ..) Как мне сделать это с помощью clpfd? Или лучше использовать метод по ссылке?

К вашему сведению: я также нашел этот http://www.metalevel.at/zurg/zurg.html У которого есть решение DCG. В нем встроено ограничение Time = ‹60, наименьшее время он не находит.


person user27815    schedule 06.10.2015    source источник


Ответы (4)


Вот версия CLP (FD), основанная на коде, на который вы указали ссылку.

Основное отличие состоит в том, что в этой версии Limit - это параметр, а не жестко заданное значение. Кроме того, он также использует гибкость ограничений CLP (FD), чтобы показать, что, по сравнению с арифметикой низкого уровня, вы можете гораздо более свободно переупорядочивать свои цели при использовании ограничений и гораздо более декларативно рассуждать о своем коде:

:- use_module(library(clpfd)).

toy_time(buzz,   5).
toy_time(woody, 10).
toy_time(rex,   20).
toy_time(hamm,  25).

moves(Ms, Limit) :-
    phrase(moves(state(0,[buzz,woody,rex,hamm],[]), Limit), Ms).

moves(state(T0,Ls0,Rs0), Limit) -->
    [left_to_right(Toy1,Toy2)],
    { T1 #= T0 + max(Time1,Time2), T1 #=< Limit,
      select(Toy1, Ls0, Ls1), select(Toy2, Ls1, Ls2),
      Toy1 @< Toy2,
      toy_time(Toy1, Time1), toy_time(Toy2, Time2) },
    moves_(state(T1,Ls2,[Toy1,Toy2|Rs0]), Limit).

moves_(state(_,[],_), _)         --> [].
moves_(state(T0,Ls0,Rs0), Limit) -->
    [right_to_left(Toy)],
    { T1 #= T0 + Time, T1 #=< Limit,
      select(Toy, Rs0, Rs1),
      toy_time(Toy, Time) },
    moves(state(T1,[Toy|Ls0],Rs1), Limit).

Пример использования с использованием итеративного углубления для поиска наиболее быстрых решений в первую очередь:

?- length(_, Limit), moves(Ms, Limit).
Limit = 60,
Ms = [left_to_right(buzz, woody), right_to_left(buzz), left_to_right(hamm, rex), right_to_left(woody), left_to_right(buzz, woody)] ;
Limit = 60,
Ms = [left_to_right(buzz, woody), right_to_left(woody), left_to_right(hamm, rex), right_to_left(buzz), left_to_right(buzz, woody)] ;
Limit = 61,
Ms = [left_to_right(buzz, woody), right_to_left(buzz), left_to_right(hamm, rex), right_to_left(woody), left_to_right(buzz, woody)] ;
etc.

Обратите внимание, что в этой версии используется комбинация ограничений CLP (FD) (для сокращения и арифметики) и встроенного обратного отслеживания Пролога, и такая комбинация совершенно законна. В некоторых случаях глобальные ограничения (например, automaton/8, упомянутые CapelliC) могут выражать проблему целиком, но сочетание ограничений с обычным поиском с возвратом также является хорошей стратегией для многих задач.

Фактически, просто опубликовать ограничения CLP (FD) в любом случае обычно недостаточно: для получения конкретных решений обычно требуется поиск (с обратным отслеживанием), предоставляемый labeling/2 в случае CLP (FD). Таким образом, это итеративное углубление похоже на поиск, который labeling/2 в противном случае выполнил бы, если бы вам удалось детерминированно выразить проблему с помощью только ограничений CLP (FD).

Красиво, мы также можем показать:

?- Limit #< 60, moves(Ms, Limit).
false.

РЕДАКТИРОВАТЬ: поскольку жажда automaton/8 кажется почти неутолимой среди заинтересованных пользователей ограничений CLP (FD), что приятно, я также создал для вас решение с этим мощным глобальным ограничением. Если вам это интересно, пожалуйста, проголосуйте за ответ @CaplliC, так как у него была первоначальная идея использовать для этого automaton/8. Идея состоит в том, чтобы позволить каждому возможному (и разумному) движению одной или двух игрушек соответствовать уникальному целому числу, и эти движения вызывают переходы между различными состояниями автомата. Обратите внимание, что сторона фонарика также играет важную роль в состояниях. Кроме того, мы снабжаем каждую дугу арифметическим выражением, чтобы отслеживать затраченное время. Попробуйте ?- arc(_, As). увидеть дуги этого автомата.

:- use_module(library(clpfd)).

toy_time(b,  5).
toy_time(w, 10).
toy_time(r, 20).
toy_time(h, 25).

toys(Toys) :- setof(Toy, T^toy_time(Toy, T), Toys).

arc0(arc0(S0,M,S)) :-
    state(S0),
    state0_movement_state(S0, M, S).

arcs(V, Arcs) :-
    findall(Arc0, arc0(Arc0), Arcs0),
    movements(Ms),
    maplist(arc0_arc(V, Ms), Arcs0, Arcs).

arc0_arc(C, Ms, arc0(S0,M,S), arc(S0, MI, S, [C+T])) :-
    movement_time(M, T),
    nth0(MI, Ms, M).

movement_time(left_to_right(Toy), Time) :- toy_time(Toy, Time).
movement_time(left_to_right(T1,T2), Time) :-
    Time #= max(Time1,Time2),
    toy_time(T1, Time1),
    toy_time(T2, Time2).
movement_time(right_to_left(Toy), Time) :- toy_time(Toy, Time).


state0_movement_state(lrf(Ls0,Rs0,left), left_to_right(T), lrf(Ls,Rs,right)) :-
    select(T, Ls0, Ls),
    sort([T|Rs0], Rs).
state0_movement_state(lrf(Ls0,Rs0,left), left_to_right(T1,T2), S) :-
    state0_movement_state(lrf(Ls0,Rs0,left), left_to_right(T1), lrf(Ls1,Rs1,_)),
    state0_movement_state(lrf(Ls1,Rs1,left), left_to_right(T2), S),
    T1 @< T2.
state0_movement_state(lrf(Ls0,Rs0,right), right_to_left(T), lrf(Ls,Rs,left)) :-
    select(T, Rs0, Rs),
    sort([T|Ls0], Ls).

movements(Moves) :-
    toys(Toys),
    findall(Move, movement(Toys, Move), Moves).

movement(Toys, Move) :-
    member(T, Toys),
    (   Move = left_to_right(T)
    ;   Move = right_to_left(T)
    ).
movement(Toys0, left_to_right(T1, T2)) :-
    select(T1, Toys0, Toys1),
    member(T2, Toys1),
    T1 @< T2.

state(lrf(Lefts,Rights,Flash)) :-
    toys(Toys),
    phrase(lefts(Toys), Lefts),
    foldl(select, Lefts, Toys, Rights),
    ( Flash = left ; Flash = right ).

lefts([]) --> [].
lefts([T|Ts]) --> ( [T] | [] ), lefts(Ts).

И теперь, наконец-то, мы, наконец, можем использовать automaton/8, которое мы так сильно желаем, для решения, которое мы искренне считаем достойным нести баннер "CLP (FD)" оргиастически. смешанный с min/1 опцией labeling/2:

?- time((arcs(C, Arcs),
         length(Vs, _),
         automaton(Vs, _, Vs, [source(lrf([b,h,r,w],[],left)),
                               sink(lrf([],[b,h,r,w],right))],
                   Arcs, [C], [0], [Time]),
         labeling([min(Time)], Vs))).

уступая:

857,542 inferences, 0.097 CPU in 0.097 seconds(100% CPU, 8848097 Lips)
Arcs = [...],
Time = 60,
Vs = [10, 1, 11, 7, 10] ;
etc.

Я оставляю перевод таких решений в удобочитаемые переходы состояний в качестве простого упражнения (~ 3 строки кода).

Для дополнительного удовлетворения, это намного быстрее, чем исходная версия с простым Prolog, для которой у нас были:

?- time((length(_, Limit), moves(Ms, Limit))).
1,666,522 inferences, 0.170 CPU in 0.170 seconds (100% CPU, 9812728 Lips)

Мораль этой истории: если ваше простое решение Prolog требует более десятой доли секунды для получения решений, вам лучше научиться использовать одно из самых сложных и мощных глобальных ограничений, чтобы сократить время выполнения на несколько миллисекунды! :-)

Но если серьезно, то этот пример показывает, что распространение ограничений может окупиться очень скоро, даже для сравнительно небольших пространств поиска. Вы можете ожидать еще большего относительного выигрыша при решении более сложных задач поиска с помощью CLP (FD).

Однако обратите внимание, что вторая версия, хотя и распространяет ограничения более глобально в определенном смысле, не имеет важной функции, которая также связана с распространением и сокращением: раньше мы могли напрямую использовать программу, чтобы показать, что не существует решения, которое требует меньше более 60 минут с использованием прямого и естественного запроса (?- Limit #< 60, moves(Ms, Limit)., который не удался). Это следует из второй программы только неявно, потому что мы знаем, что ceteris paribus более длинные списки могут не более увеличить время. Однако, к сожалению, отдельный звонок length/2 не получил напоминания.

С другой стороны, вторая версия способна доказать что-то, что в каком-то смысле, по крайней мере, столь же впечатляюще, и делает это более эффективно и несколько более прямо, чем первая версия: даже не строя единого явного решения, мы можем использовать вторая версия, чтобы показать, что любое решение (если оно есть) требует как минимум 5 пересечений:

?- time((arcs(C, Arcs),
         length(Vs, L),
         automaton(Vs, _, Vs, [source(lrf([b,h,r,w],[],left)),
                               sink(lrf([],[b,h,r,w],right))],
         Arcs, [C], [0], [Time]))).

уступая:

331,495 inferences, 0.040 CPU in 0.040 seconds (100% CPU, 8195513 Lips)
...,
L = 5
... .

Это работает только путем распространения ограничений и не требует labeling/2!

person mat    schedule 07.10.2015
comment
Спасибо, этот ответ делает то, что я надеялся, и отвечает на вопрос. Однако я заинтригован, увидев, как это можно сделать с помощью автоматов ... так что я пока не соглашусь ... - person user27815; 07.10.2015
comment
Я тоже с нетерпением жду экспериментов с automaton/8. Ясно одно: любое такое решение также должно будет использовать итеративное углубление (или другую стратегию полного поиска), чтобы найти самое короткое или самое быстрое решение. Это связано с тем, что для любого вызова automaton/8 требуется, чтобы подпись, то есть описываемый список, была в достаточной степени создана. Таким образом, по крайней мере, должна быть известна длина списка, и фактический поиск решений будет, как и в моем ответе, поэтому, вероятно, также начнется с ?- length(List, _) и сочетает итеративное углубление с ограничениями. - person mat; 07.10.2015
comment
Спасибо за этот ответ, так как он и ответы CapelliC помогли мне узнать о clpfd. - person user27815; 09.10.2015

Я думаю, что моделирование этой головоломки с помощью CLPFD может быть выполнено с помощью automaton / 8. На Прологе я бы написал

escape_zurg(T,S) :-
    aggregate(min(T,S), (
     solve([5,10,20,25], [], S),
     sum_timing(S, T)), min(T,S)).

solve([A, B], _, [max(A, B)]).
solve(L0, R0, [max(A, B), C|T]) :-
    select(A, L0, L1),
    select(B, L1, L2),
    append([A, B], R0, R1),
    select(C, R1, R2),
    solve([C|L2], R2, T).

sum_timing(S, T) :-
    aggregate(sum(E), member(E, S), T).

что дает это решение

?- escape_zurg(T,S).
T = 60,
S = [max(5, 10), 5, max(20, 25), 10, max(10, 5)].

редактировать

ну, автомат / 8 мне недоступен ... давайте начнем проще: что может быть простым представлением состояния? слева / справа у нас есть 4 слота, которые могут быть пустыми: итак

escape_clpfd(T, Sf) :-
    L0 = [_,_,_,_],
    Zs = [0,0,0,0],
    L0 ins 5\/10\/20\/25,
    all_different(L0),
    ...

теперь, поскольку проблема настолько проста, мы можем жестко запрограммировать изменение состояния

...
lmove(L0/Zs, 2/2, L1/R1, T1), rmove(L1/R1, 1/3, L2/R2, T2),
lmove(L2/R2, 3/1, L3/R3, T3), rmove(L3/R3, 2/2, L4/R4, T4),
lmove(L4/R4, 4/0, Zs/ _, T5),
...

первый lmove/4 должен сдвинуть 2 элемента слева направо, и после этого у нас будет 2 нуля слева и 2 справа. Время (T1) будет max(A,B), где A, B к настоящему времени инкогнито. rmove/4 аналогичен, но «вернет» в T2 единственный элемент (инкогнито), который он будет перемещать справа налево. Мы кодируем эволюцию, утверждая количество нулей на каждой стороне (кажется, нетрудно обобщить).

Завершим:

...
T #= T1 + T2 + T3 + T4 + T5,
Sf = [T1,T2,T3,T4,T5].

Теперь rmove / 4 проще, поэтому давайте закодируем его:

rmove(L/R, Lz/Rz, Lu/Ru, M) :-
    move_one(R, L, Ru, Lu, M),
    count_0s(Ru, Rz),
    count_0s(Lu, Lz).

он откладывает фактическую работу move_one / 5, а затем применяет числовое ограничение, которое мы жестко запрограммировали выше:

count_0s(L, Z) :-
    maplist(is_0, L, TF),
    sum(TF, #=, Z).

is_0(V, C) :- V #= 0 #<==> C.

is_0 / 2 усиливает условие пустого слота, то есть делает счетным значение истинности. Стоит протестировать:

?- count_0s([2,1,1],X).
X = 0.

?- count_0s([2,1,C],1).
C = 0.

?- count_0s([2,1,C],2).
false.

Кодирование move_one / 5 в CLP (FD) кажется сложным. Здесь действительно уместен недетерминизм Пролога ...

move_one(L, R, [Z|Lt], [C|Rt], C) :-
    select(C, L, Lt), is_0(C, 0),
    select(Z, R, Rt), is_0(Z, 1).

select / 3 это чистый предикат, и Prolog вернется, когда для маркировки потребуется ...

Минимизации нет, но ее легко добавить после того, как мы получим решения. Пока все кажется мне «логичным». Но конечно...

?- escape_clpfd(T, S).
false.

Итак, вот драконы ...

?- spy(lmove),escape_clpfd(T, S).
% Spy point on escape_zurg:lmove/4
 * Call: (9) escape_zurg:lmove([_G12082{clpfd = ...}, _G12164{clpfd = ...}, _G12246{clpfd = ...}, _G12328{clpfd = ...}]/[0, 0, 0, 0], 2/2, _G12658/_G12659, _G12671) ?  creep
   Call: (10) escape_zurg:move_one([_G12082{clpfd = ...}, _G12164{clpfd = ...}, _G12246{clpfd = ...}, _G12328{clpfd = ...}], [0, 0, 0, 0], _G12673, _G12674, _G12661) ? sskip

... и т. д. и т. д.

Извините, опубликую решение, если у меня будет свободное время для отладки ...

edit было несколько ошибок ... с этим lmove / 4

lmove(L/R, Lz/Rz, Lu/Ru, max(A, B)) :-
    move_one(L, R, Lt, Rt, A),
    move_one(Lt, Rt, Lu, Ru, B),
    count_0s(Lu, Lz),
    count_0s(Ru, Rz).

по крайней мере, мы начинаем получать решения (добавлены переменные в интерфейс для маркировки извне ...)

escape_clpfd(T, Sf, L0) :- ...

?- escape_clpfd(T, S, Vs), label(Vs).
T = 85,
S = [max(5, 10), 10, max(10, 20), 20, max(20, 25)],
Vs = [5, 10, 20, 25] ;
T = 95,
S = [max(5, 10), 10, max(10, 25), 25, max(25, 20)],
Vs = [5, 10, 25, 20] ;
...

редактировать

приведенный выше код работает, но очень медленно:

?- time((escape_clpfd(60, Sf, L0),label(L0))).
% 15,326,054 inferences, 5.466 CPU in 5.485 seconds (100% CPU, 2803917 Lips)
Sf = [max(5, 10), 10, max(20, 25), 5, max(5, 10)],
L0 = [5, 10, 20, 25] 

с этим изменением на move_one / 5:

move_one([L|Ls], [R|Rs], [R|Ls], [L|Rs], L) :-
    L #\= 0,
    R #= 0.
move_one([L|Ls], [R|Rs], [L|Lu], [R|Ru], E) :-
    move_one(Ls, Rs, Lu, Ru, E).

У меня производительность лучше:

?- time((escape_clpfd(60, Sf, L0),label(L0))).
% 423,394 inferences, 0.156 CPU in 0.160 seconds (97% CPU, 2706901 Lips)
Sf = [max(5, 10), 5, max(20, 25), 10, max(5, 10)],
L0 = [5, 10, 20, 25] 

затем, добавив к lmove / 4

... A #< B, ...

я получил

% 233,953 inferences, 0.089 CPU in 0.095 seconds (94% CPU, 2621347 Lips)
Sf = [max(5, 10), 5, max(20, 25), 10, max(5, 10)],

в целом это все еще намного медленнее, чем мое чистое решение Prolog ...

редактировать

другие небольшие улучшения:

?- time((escape_clpfd(60, Sf, L0),maplist(#=,L0,[5,10,20,25]))).
% 56,583 inferences, 0.020 CPU in 0.020 seconds (100% CPU, 2901571 Lips)
Sf = [max(5, 10), 5, max(20, 25), 10, max(5, 10)],

где all_different / 1 заменено на

...
chain(L0, #<),
...

Еще одно улучшение: считать обе стороны на нули бесполезно: удаляя (произвольно) одну сторону как в lmove, так и в rmove, мы получаем

% 35,513 inferences, 0.014 CPU in 0.014 seconds (100% CPU, 2629154 Lips)
Sf = [max(5, 10), 5, max(20, 25), 10, max(5, 10)],

редактировать

Ради удовольствия, вот то же самое чистое (за исключением агрегации) решение Prolog, использующее простой детерминированный «подъем» переменных (любезно предоставлено lifter '):

:- use_module(carlo(snippets/lifter)).

solve([A, B], _, [max(A, B)]).
solve(L0, R0, [max(A, B), C|T]) :-
    solve([C|select(B, select(A, L0, °), °)],
          select(C, append([A, B], R0, °), °),
          T).

кстати, довольно быстро:

?- time(escape_zurg(T,S)).
% 50,285 inferences, 0.065 CPU in 0.065 seconds (100% CPU, 769223 Lips)
T = 60,
S = [max(5, 10), 5, max(20, 25), 10, max(10, 5)].

(абсолютная синхронизация не так хороша, потому что я использую SWI-Prolog, скомпилированный для отладки)

person CapelliC    schedule 07.10.2015
comment
Это здорово, но я пытаюсь лучше понять библиотеку clpfd. Спасибо за совет по использованию automaton / 8, было бы здорово увидеть это мышление немного подробнее .. - person user27815; 07.10.2015
comment
Спасибо за вашу работу над этим - это очень помогло мне понять мысли, необходимые для использования clpfd. - person user27815; 09.10.2015
comment
s(X) за то, что увидел связь с automaton/8, и за другие очень полезные мысли! - person mat; 09.10.2015
comment
@mat: спасибо! Как можно скорее изучу ваше решение по автоматизации / 3. - person CapelliC; 09.10.2015

Я думаю, что @mat дал хороший ответ на то, что я изначально пытался сделать, но я попытался также использовать automaton / 4 вместе с поиском с возвратом для добавления дуг. Это все, что я получил. Но я получаю ошибку ERROR: Arguments are not sufficiently instantiated при вызове bridge/2. Просто разместите здесь, если у кого-то есть какие-либо комментарии по этому подходу или он знает, почему это может вызвать эту ошибку, или если я использую automaton/4 совершенно неправильно!

fd_length(L, N) :-
  N #>= 0,
  fd_length(L, N, 0).

fd_length([], N, N0) :-
  N #= N0.
fd_length([_|L], N, N0) :-
  N1 is N0+1,
  N #>= N1,
fd_length(L, N, N1).

left_to_right_arc(L0,R0,Arc):-
  LenL#=<4,
  fd_length(L0,LenL),
  LenR #=4-LenL,
  fd_length(R0,LenR),
  L0 ins 5\/10\/20\/25,
  R0 ins 5\/10\/20\/25,
  append(L0,R0,All),
  all_different(All),
  Before =[L0,R0],
  select(A,L0,L1),
  select(B,L1,L2),
  append([A,B],R0,R1),
  After=[L2,R1],
  Cost #=max(A,B),
  Arc =arc(Before,Cost,After).

right_to_left_arc(L0,R0,Arc):-
  LenL#=<4,
  fd_length(L0,LenL),
  LenR #=4-LenL,
  fd_length(R0,LenR),
  L0 ins 5\/10\/20\/25,
  R0 ins 5\/10\/20\/25,
  append(L0,R0,All),
  all_different(All),
  Before=[L0,R0],
  select(A,R0,R1),
  append([A],L0,L1),
  After=[L1,R1],
  Cost#=A,
  Arc =arc(After,Cost,Before).

pair_of_arcs(Arcs):-
  left_to_right_arc(_,_,ArcLR),
  right_to_left_arc(_,_,ArcRL),
  Arcs =[ArcLR,ArcRL].

pairs_of_arcs(Pairs):-
  L#>=1,
  fd_length(Pairs,L),
  once(maplist(pair_of_arcs,Pairs)).

bridge(Vs,Arcs):-
  pairs_of_arcs(Arcs),
  flatten(Arcs,FArcs),
  automaton(Vs,[source([[5,10,20,25],[]]),sink([[],[5,10,20,25]])],
      FArcs).
person user27815    schedule 07.10.2015
comment
Определенно интересная идея - смоделировать это с помощью переходов состояний конечного автомата, так что +1! Чтобы определить причину проблемы, попробуйте ?- gtrace, your_goal. и выполните пошаговую работу по вашей программе в графическом трассировщике SWI-Prolog. Кстати, в унификации в конце некоторых пунктов действительно нет необходимости. Просто запишите эти термины прямо на их естественном месте. Например: left_to_right_arc(L0, R0, arc(Before,Cost,After)) :- ..., т.е. вы можете переместить эти термины прямо в заголовок предложения, поскольку они больше нигде не нужны. - person mat; 07.10.2015

Это не ответ на использование CLP (FD), а просто чтобы показать два решения, которые существуют для этой головоломки со стоимостью, равной или меньшей 60 (текст слишком велик для добавления в комментарий).

Есть несколько вариантов этой головоломки. Logtalk включает один в своем searching/bridge.lgt примере с другим набором символов и соответствующим временем пересечения моста. Но мы можем исправить это, чтобы вместо этого решить вариант этого вопроса (используя текущую версию Logtalk git):

?- set_logtalk_flag(complements, allow).
true.

?- {searching(loader)}.
...
% (0 warnings)
true.

?- create_category(patch, [complements(bridge)], [], [initial_state(start, ([5,10,20,25], left, [])), goal_state(end, ([], right, [5,10,20,25]))]).
true.

?- performance::init, bridge::initial_state(Initial), hill_climbing(60)::solve(bridge, Initial, Path, Cost), bridge::print_path(Path), performance::report.
5 10 20 25  lamp _|____________|_ 
20 25  _|____________|_ lamp 5 10 
5 20 25  lamp _|____________|_ 10 
5  _|____________|_ lamp 10 20 25 
5 10  lamp _|____________|_ 20 25 
 _|____________|_ lamp 5 10 20 25 
solution length: 6
state transitions (including previous solutions): 113
ratio solution length / state transitions: 0.05309734513274336
minimum branching degree: 1
average branching degree: 5.304347826086956
maximum branching degree: 10
time: 0.004001000000000032
Initial =  ([5, 10, 20, 25], left, []),
Path = [([5, 10, 20, 25], left, []),  ([20, 25], right, [5, 10]),  ([5, 20, 25], left, [10]),  ([5], right, [10, 20, 25]),  ([5, 10], left, [20, 25]),  ([], right, [5|...])],
Cost = 60 ;
5 10 20 25  lamp _|____________|_ 
20 25  _|____________|_ lamp 5 10 
10 20 25  lamp _|____________|_ 5 
10  _|____________|_ lamp 5 20 25 
5 10  lamp _|____________|_ 20 25 
 _|____________|_ lamp 5 10 20 25 
solution length: 6
state transitions (including previous solutions): 219
ratio solution length / state transitions: 0.0273972602739726
minimum branching degree: 1
average branching degree: 5.764705882352941
maximum branching degree: 10
time: 0.0038759999999999906
Initial =  ([5, 10, 20, 25], left, []),
Path = [([5, 10, 20, 25], left, []),  ([20, 25], right, [5, 10]),  ([10, 20, 25], left, [5]),  ([10], right, [5, 20, 25]),  ([5, 10], left, [20, 25]),  ([], right, [5|...])],
Cost = 60 ;
false.
person Paulo Moura    schedule 07.10.2015