Prolog company/employee 调度问题

Prolog company/employee scheduling problem

我在 prolog 中有一个半复杂的班次安排问题。 据我所知,它可以用 CLF 解决,但我不是很熟悉,网上的资源也没有真正帮助我。

问题是公司有50名员工,每个员工要么上早班(M),要么晚班(E)、夜班(N)或休息日(R) . 该问题有 2 个约束条件:至少 15 名员工必须在早班工作 (M),10晚上一个(E)和8个晚上一个(N)并且没有员工可以上夜班(N) 第二天要上早班(M).

它要求通过满足上述约束并存在多个解决方案来生成 30 天的时间表。

有什么方法可以解决这个问题,我如何使用 prolog 中的代码来实现它?

非常感谢!

这是一个完整的解决方案(使用 swi-prolog):

days_in_month(30).
employees_num(50).


go :-
    days_in_month(Days),
    length(M, Days),
    days(M),
    show_days(M).


days([D1, D2|T]) :-
    two_days(D1, D2),
    (T = [] ; days([D2|T])).


other_day_constraints(D) :-
    day_constraint(10, e, D),
    maplist(rest_if_not_work, D).


day_constraint(Min, Element, Lst) :-
    employees_num(EmpsNum),
    list_has_ge_elements_being(Min, Element, EmpsNum, Lst).


two_days(D1, D2) :-
    % Set the full number of employees, otherwise prevent_double_shift can shorten the list
    employees_num(EmpsNum),
    length(D1, EmpsNum),
    length(D2, EmpsNum),

    % Pass the 2-day constraint first
    day_constraint(8, n, D1),
    prevent_double_shift(D1, D2),
    day_constraint(15, m, D2),
    
    % Remainder of the day constraints
    day_constraint(15, m, D1),
    day_constraint(8, n, D2),

    other_day_constraints(D1),
    other_day_constraints(D2).


prevent_double_shift([], []).
prevent_double_shift([H1|T1], [H2|T2]) :-
    (H1 == n -> dif(H2, m) ; true),
    prevent_double_shift(T1, T2).


rest_if_not_work(E) :-
    (var(E) -> E = r ; true).


show_days([]).
show_days([D|T]) :-
    show_day(D),
    show_days(T).


show_day(D) :-
    forall(member(E, D), (upcase_atom(E, U), write(U))),
    nl.


% Potentially use E as the first element
list_length_abs(N, E, [E|_]) :-
    N > 1.
list_length_abs(0, _, []).
list_length_abs(1, E, [E]).


list_has_ge_elements_being(0, _E, MaxLen, L) :-
    !,
    MaxLen >= 0,
    % Don't need E
    list_length_abs(MaxLen, _, L).

list_has_ge_elements_being(Min, E, MaxLen, [H|T]) :-
    must_be(integer, MaxLen),
    MaxLen >= 1,
    must_be(integer, Min),
    Min >= 1,
    Min =< MaxLen,
    list_has_ge_elements_being_(Min, E, MaxLen, [H|T]).

list_has_ge_elements_being_(Min, E, MaxLen, L) :-
    Min =:= MaxLen ->
        length(L, Min),
        maplist(=(E), L)
    ; MaxLen =:= 1 ->
        list_has_ge_elements_being_maxlen1_(Min, L, E)
    ;   list_has_ge_elements_being_flex_(Min, E, MaxLen, L).


list_has_ge_elements_being_maxlen1_(0, [_], _).
list_has_ge_elements_being_maxlen1_(1, [E], E).

list_has_ge_elements_being_flex_(Min, H, MaxLen, [H|T]) :-
    % Loop while unifying the element
    succ(Min0, Min),
    succ(MaxLen0, MaxLen),
    % Check whether can terminate the list early
    (Min0 =:= 0 -> true ; list_has_ge_elements_being_(Min0, H, MaxLen0, T)).

list_has_ge_elements_being_flex_(Min, E, MaxLen, [H|T]) :-
    % Ensure that the element is not already the intended value
    \+ E == H,
    succ(MaxLen0, MaxLen),
    list_has_ge_elements_being_(Min, E, MaxLen0, T).