1+ use selen:: prelude:: * ;
2+
3+ type Employee = ( & ' static str , usize , usize , bool ) ; // (name, min_shift, max_shift, is_supervisor)
4+ type Schedule = Vec < Vec < Vec < VarId > > > ; // [day][shift][employee]
5+
6+ fn can_work_shift ( employee : & Employee , shift : usize ) -> bool {
7+ shift >= employee. 1 && shift <= employee. 2
8+ }
9+
10+ fn create_work_variable ( m : & mut Model , can_work : bool ) -> VarId {
11+ if can_work {
12+ m. int ( 0 , 1 ) // Binary: works this shift or not
13+ } else {
14+ m. int ( 0 , 0 ) // Cannot work - fixed to 0
15+ }
16+ }
17+
18+ fn build_schedule ( m : & mut Model , staff : & [ Employee ] , days : usize , shifts : usize ) -> Schedule {
19+ let mut schedule = Vec :: new ( ) ;
20+
21+ for _day in 0 ..days {
22+ let mut day_schedule = Vec :: new ( ) ;
23+
24+ for shift in 0 ..shifts {
25+ let shift_workers: Vec < VarId > = staff. iter ( )
26+ . map ( |employee| create_work_variable ( m, can_work_shift ( employee, shift) ) )
27+ . collect ( ) ;
28+ day_schedule. push ( shift_workers) ;
29+ }
30+
31+ schedule. push ( day_schedule) ;
32+ }
33+
34+ schedule
35+ }
36+
37+ fn add_staffing_constraints ( m : & mut Model , schedule : & Schedule , needed : & [ i32 ] ) {
38+ for ( _day, day_schedule) in schedule. iter ( ) . enumerate ( ) {
39+ for ( shift, workers) in day_schedule. iter ( ) . enumerate ( ) {
40+ let worker_sum = m. sum ( workers) ;
41+ m. c ( worker_sum) . eq ( int ( needed[ shift] ) ) ;
42+ }
43+ }
44+ }
45+
46+ /// Ensures each shift has at least one supervisor working
47+ /// For every shift on every day, at least one supervisor must be scheduled
48+ fn add_supervisor_constraints ( m : & mut Model , schedule : & Schedule , staff : & [ Employee ] ) {
49+ // Go through each day in the schedule
50+ for day_schedule in schedule {
51+ // Go through each shift in that day (morning, afternoon, night)
52+ for shift_workers in day_schedule {
53+ // Find all supervisor variables for this specific shift
54+ // We look at each employee and check if they're a supervisor
55+ let supervisors: Vec < VarId > = staff. iter ( )
56+ . enumerate ( ) // Get employee index and their data
57+ . filter_map ( |( emp_id, ( _, _, _, is_supervisor) ) | {
58+ if * is_supervisor {
59+ // This employee is a supervisor, so include their work variable for this shift
60+ Some ( shift_workers[ emp_id] )
61+ } else {
62+ // Regular employee, skip them
63+ None
64+ }
65+ } )
66+ . collect ( ) ;
67+
68+ // Only add constraint if we have supervisors available for this shift
69+ if !supervisors. is_empty ( ) {
70+ // Constraint: sum of supervisor work variables >= 1
71+ // This means at least one supervisor must work this shift
72+ let supervisor_sum = m. sum ( & supervisors) ;
73+ m. c ( supervisor_sum) . ge ( int ( 1 ) ) ;
74+ }
75+ }
76+ }
77+ }
78+
79+ fn add_workload_constraints ( m : & mut Model , schedule : & Schedule , staff : & [ Employee ] ) {
80+ for ( emp_id, _) in staff. iter ( ) . enumerate ( ) {
81+ for day_schedule in schedule {
82+ let daily_shifts: Vec < VarId > = day_schedule. iter ( )
83+ . map ( |shift_workers| shift_workers[ emp_id] )
84+ . collect ( ) ;
85+ let daily_sum = m. sum ( & daily_shifts) ;
86+ m. c ( daily_sum) . le ( int ( 2 ) ) ;
87+ }
88+ }
89+ }
90+
91+ fn main ( ) {
92+ println ! ( "Employee Scheduling System" ) ;
93+ println ! ( "==========================" ) ;
94+
95+ let mut m = Model :: default ( ) ;
96+
97+ let staff: [ Employee ; 9 ] = [
98+ ( "Alice" , 0 , 2 , true ) , // Any shift, supervisor
99+ ( "Bob" , 0 , 1 , false ) , // Morning/afternoon only
100+ ( "Carol" , 1 , 2 , true ) , // Afternoon/night, supervisor
101+ ( "David" , 0 , 2 , false ) , // Any shift
102+ ( "Eve" , 2 , 2 , false ) , // Night only
103+ ( "Frank" , 0 , 1 , true ) , // Morning/afternoon, supervisor
104+ ( "Grace" , 0 , 2 , false ) , // Any shift
105+ ( "Henry" , 1 , 2 , true ) , // Afternoon/night, supervisor
106+ ( "Ivy" , 0 , 1 , false ) , // Morning/afternoon only
107+ ] ;
108+
109+ let days = 2 ;
110+ let shifts = 3 ; // 0=Morning, 1=Afternoon, 2=Night
111+ let needed = [ 3 , 4 , 3 ] ; // Staff needed per shift
112+
113+ // Build the scheduling variables
114+ let schedule = build_schedule ( & mut m, & staff, days, shifts) ;
115+
116+ // Add all constraints using helper functions
117+ add_staffing_constraints ( & mut m, & schedule, & needed) ;
118+ add_supervisor_constraints ( & mut m, & schedule, & staff) ;
119+ add_workload_constraints ( & mut m, & schedule, & staff) ;
120+
121+ println ! ( "Solving..." ) ;
122+
123+ match m. solve ( ) {
124+ Ok ( solution) => {
125+ println ! ( "Schedule found!\n " ) ;
126+
127+ // Calculate column widths based on name lengths
128+ let mut col_widths = Vec :: new ( ) ;
129+ for ( name, _, _, is_supervisor) in & staff {
130+ let display_name = if * is_supervisor {
131+ format ! ( "{}(S)" , name)
132+ } else {
133+ name. to_string ( )
134+ } ;
135+ col_widths. push ( display_name. len ( ) . max ( 3 ) ) ; // minimum 3 chars for X/-
136+ }
137+
138+ // Print header
139+ println ! ( "Employee Schedule Table:" ) ;
140+ print ! ( "┌───────────" ) ;
141+ for & width in & col_widths {
142+ print ! ( "┬{}" , "─" . repeat( width + 2 ) ) ;
143+ }
144+ println ! ( "┐" ) ;
145+
146+ print ! ( "│ Shift " ) ;
147+ for ( i, ( name, _, _, is_supervisor) ) in staff. iter ( ) . enumerate ( ) {
148+ let display = if * is_supervisor {
149+ format ! ( "{}(S)" , name)
150+ } else {
151+ name. to_string ( )
152+ } ;
153+ print ! ( "│ {:<width$} " , display, width = col_widths[ i] ) ;
154+ }
155+ println ! ( "│" ) ;
156+
157+ print ! ( "├───────────" ) ;
158+ for & width in & col_widths {
159+ print ! ( "┼{}" , "─" . repeat( width + 2 ) ) ;
160+ }
161+ println ! ( "┤" ) ;
162+
163+ // Print each shift as a row
164+ let shift_names = [ "Morning" , "Afternoon" , "Night" ] ;
165+ for day in 0 ..days {
166+ for shift in 0 ..shifts {
167+ let shift_label = if days == 1 {
168+ format ! ( "{:<9}" , shift_names[ shift] )
169+ } else {
170+ format ! ( "{} D{}" , & shift_names[ shift] [ ..3 ] , day + 1 )
171+ } ;
172+ print ! ( "│ {:<9} " , shift_label) ;
173+
174+ for emp_id in 0 ..staff. len ( ) {
175+ let works = solution. get :: < i32 > ( schedule[ day] [ shift] [ emp_id] ) == 1 ;
176+ let symbol = if works { "X" } else { "-" } ;
177+ print ! ( "│ {:<width$} " , symbol, width = col_widths[ emp_id] ) ;
178+ }
179+ println ! ( "│" ) ;
180+ }
181+
182+ // No separator between days to avoid empty rows
183+ }
184+
185+ print ! ( "└───────────" ) ;
186+ for & width in & col_widths {
187+ print ! ( "┴{}" , "─" . repeat( width + 2 ) ) ;
188+ }
189+ println ! ( "┘" ) ;
190+
191+ // Show daily schedule breakdown
192+ println ! ( "\n Daily Breakdown:" ) ;
193+ let shift_names = [ "Morning" , "Afternoon" , "Night" ] ;
194+
195+ for day in 0 ..days {
196+ println ! ( "\n Day {}:" , day + 1 ) ;
197+ for shift in 0 ..shifts {
198+ let workers: Vec < _ > = staff. iter ( )
199+ . enumerate ( )
200+ . filter_map ( |( emp_id, ( name, _, _, is_sup) ) | {
201+ if solution. get :: < i32 > ( schedule[ day] [ shift] [ emp_id] ) == 1 {
202+ let role_marker = if * is_sup { "(S)" } else { "" } ;
203+ Some ( format ! ( "{}{}" , name, role_marker) )
204+ } else {
205+ None
206+ }
207+ } )
208+ . collect ( ) ;
209+
210+ println ! ( " {:<10}: {} (needed: {})" ,
211+ shift_names[ shift] ,
212+ if workers. is_empty( ) { "No one assigned" . to_string( ) } else { workers. join( ", " ) } ,
213+ needed[ shift]
214+ ) ;
215+ }
216+ }
217+ }
218+ Err ( _) => println ! ( "No solution found!" ) ,
219+ }
220+ }
0 commit comments