prologschedulingtimetable

shift scheduling prolog company 50 people 30 days at least to rest days a week


I have a semi complex shift scheduling problem in prolog. From what I saw it can be solved with CLF but I am not that familiar and the resources online didn't really help me.

The problem states that the company has 50 employees and that each employee can either work in the morning shift(M), the evening shift(E), the night shift(N) or have a rest day(R). The problem has 2 constraints: That at least 15 employees must work at the morning shift(M), 10 in the evening one(E) and 8 in the night one(N) and that no employee can work the night shift(N) and have a morning shift(M) the next day.Also in a period of 7 days an employee must have at least 2 days off fro example from day 1 to 7 atleast two R and the same from 2 to 8.

It asks to produce a 30 day schedule by satisfying the above constraints and that multiple solutions exist.

What could be some way to approach the problem and how could I implement it using code in prolog?

Thank you very much!

Here a solution with out the last task

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.


list_has_ge_elements_being(Min, Elem, MaxLen, L) :-
    list_has_ge_elements_being_(L, Min, Elem, MaxLen).

list_has_ge_elements_being_(L, Min, Elem, Min) :-
    !,
    length(L, Min),
    maplist(=(Elem), L).
list_has_ge_elements_being_(_L, 0, _Elem, _MaxLen).
list_has_ge_elements_being_([H|T], Min, Elem, MaxLen) :-
    Min @> 0,
    MaxLen @> Min,
    (   H = Elem,
        Min0 is Min - 1
    ;   Min0 = Min
    ),
    MaxLen0 is MaxLen - 1,
    list_has_ge_elements_being_(T, Min0, Elem, MaxLen0).

Solution

  • Here's part of the puzzle - I'm sure you can figure out the rest:

    split_list_length_excl_rem(SegLen, L, Segs) :-
        length(L, Len),
        between(1, Len, SegLen),
        split_list_length_excl_rem_(SegLen, Len, L, Segs).
    
    split_list_length_excl_rem_(SegLen, Len, L, Segs) :-
        (   Len @< SegLen
        ->  Segs = []
        ;   length(Seg, SegLen),
            split_list_length_excl_rem_seg_(Seg, L, R),
            Segs = [Seg|SegsT],
            Len0 is Len - SegLen,
            split_list_length_excl_rem_(SegLen, Len0, R, SegsT)
        ).
    
    split_list_length_excl_rem_seg_([], R, R).
    split_list_length_excl_rem_seg_([H|T], [H|LT], R) :-
        split_list_length_excl_rem_seg_(T, LT, R).
    

    Results in swi-prolog:

    ?- numlist(1, 30, NL), split_list_length_excl_rem(7, NL, S).
    NL = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
    S = [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21], [22, 23, 24, 25, 26, 27, 28]].
    

    Oops, misread the week bit, this is more appropriate:

    sliding_segment(SegLen, L, Seg) :-
        length(L, Len),
        between(1, Len, SegLen),
        length(Seg, SegLen),
        sliding_segment_(L, Seg).
    
    sliding_segment_([H|T], Seg) :-
        fill_list_copy(Seg, [H|T]).
    sliding_segment_([_|T], Seg) :-
        sliding_segment_(T, Seg).
    
    fill_list_copy([], _).
    fill_list_copy([H|T], [H|R]) :-
        fill_list_copy(T, R).
    

    Results in swi-prolog:

    ?- numlist(1, 30, NL), sliding_segment(7, NL, Seg).
    Seg = [1, 2, 3, 4, 5, 6, 7] ;
    Seg = [2, 3, 4, 5, 6, 7, 8] ;
    Seg = [3, 4, 5, 6, 7, 8, 9] ;
    ...
    Seg = [23, 24, 25, 26, 27, 28, 29] ;
    Seg = [24, 25, 26, 27, 28, 29, 30] ;
    false.