package main.java.ortools_mindopt_demo;
import com.google.ortools.Loader;
import com.google.ortools.sat.*;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.IntStream;
public class EmployeeSchedulingProblem {
public int n_shifts;
public int n_days;
public int n_employees;
int[] days;
int[] shifts;
int[] employees;
int[][] demandOfEmployees;
public static Logger logger = Logger.getLogger("myLogger");
public int getDemandOfEmployees(int day, int shift) {
return demandOfEmployees[day][shift];
}
public EmployeeSchedulingProblem() throws IOException {
demandOfEmployees = this.readFile();
employees = IntStream.range(0, n_employees).toArray();
days = IntStream.range(0, n_days).toArray();
shifts = IntStream.range(0, n_shifts).toArray();
}
public int[][] readFile() throws IOException {
this.n_shifts = 0;
try (Reader reader = Files.newBufferedReader(Paths.get("src/main/java/mindoptdemo/班次.csv"))) {
Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(reader);
records.iterator().next(); // 跳过第一行
for (CSVRecord record : records) {
String shift = (record.get(0)); // 星期1到星期7,索引为0,故-1
n_shifts += 1;
}
} catch (IOException e) {
logger.warning(e.getMessage());
}
// 调度周期:7天,3班倒
this.n_days = (int) Files.lines(Paths.get(new File("src/main/java/mindoptdemo/需求人数.csv").getPath())).count() - 1;
int[][] demandOfEmps = new int[n_days][n_shifts];
// commons-csv读取csv文件,需要导入依赖
try (Reader reader = Files.newBufferedReader(Paths.get("src/main/java/mindoptdemo/需求人数.csv"))) {
Iterable<CSVRecord> records = CSVFormat.DEFAULT.parse(reader);
records.iterator().next(); // 跳过第一行
for (CSVRecord record : records) {
int day = Integer.parseInt(record.get(0)) - 1; // 星期1到星期7,索引为0,故-1
int morningShiftEmpNum = Integer.parseInt(record.get(1)); // 早班需要员工的数量
int middleShiftEmpNum = Integer.parseInt(record.get(2)); // 中班需要员工的数量
int nightShiftEmpNum = Integer.parseInt(record.get(3)); // 晚班需要员工的数量
//保存至二维数组,某天某班次需要的员工数量
demandOfEmps[day][0] = morningShiftEmpNum;
demandOfEmps[day][1] = middleShiftEmpNum;
demandOfEmps[day][2] = nightShiftEmpNum;
this.n_employees += morningShiftEmpNum + middleShiftEmpNum + nightShiftEmpNum;
}
this.n_employees = (int) Math.ceil((double) (this.n_employees) / 5) + 1;
} catch (IOException e) {
logger.info(e.getMessage());
}
return demandOfEmps;
}
public void orToolssolve() {
Loader.loadNativeLibraries();
// 声明模型
CpModel model = new CpModel();
// 初始化决策变量
Literal[][][] x = new Literal[n_employees][n_days][n_shifts];
for (int n : employees) {
for (int d : days) {
for (int s : shifts) {
x[n][d][s] = model.newBoolVar("shifts_n" + n + "d" + d + "s" + s);
}
}
}
// 约束:每天各个班次在岗的人数符合需求
for (int day = 0; day < days.length; day++) {
for (int shift = 0; shift < shifts.length; shift++) {
LinearExprBuilder numShiftsWorked = LinearExpr.newBuilder();
for (int empNum = 0; empNum < n_employees; empNum++) {
numShiftsWorked.add(x[empNum][day][shift]);
}
model.addLinearConstraint(numShiftsWorked, this.getDemandOfEmployees(day, shift), n_employees);
}
}
// 约束:每人每天最多只有一个班次
for (int n : employees) {
for (int d : days) {
List<Literal> work = new ArrayList<>();
for (int s : shifts) {
work.add(x[n][d][s]);
}
model.addAtMostOne(work);
}
}
// 约束:前一天是晚班的,第二天不能是早班
for (int e : employees) {
for (int d : days) {
List<Literal> work = new ArrayList<>();
work.add(x[e][d][2]);
if (d == 6) {
work.add(x[e][0][0]);
} else {
work.add(x[e][d + 1][0]);
}
model.addAtMostOne(work);
}
}
// 约束:一周工作工作时间不能超过5天
for (int empNum = 0; empNum < n_employees; empNum++) {
LinearExprBuilder expr = LinearExpr.newBuilder();
for (int day = 0; day < days.length; day++) {
for (int shift = 0; shift < shifts.length; shift++) {
expr.add(x[empNum][day][shift]);
}
}
model.addLinearConstraint(expr, 0, 5);
}
// 目标:雇佣的员工最少,即有排班的班次总数最少
LinearExprBuilder obj = LinearExpr.newBuilder();
for (int n : employees) {
for (int d : days) {
for (int s : shifts) {
obj.add(x[n][d][s]);
}
}
}
model.minimize(obj);
// 求解
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
System.out.printf("%-8s", " ");
for (int d = 0; d < n_days; d++) {
System.out.printf("\t%d", d + 1);
}
System.out.println();
for (int e : employees) {
System.out.printf("employee%d\t", e + 1);
int shiftCount = 0;
for (int d : days) {
int shift = 0;
for (int s : shifts) {
if (solver.booleanValue(x[e][d][s])) {
shift = s + 1;
shiftCount += 1;
}
}
System.out.printf("%d\t", shift);
}
System.out.printf("员工%d这周上%d个班次", e + 1, shiftCount);
System.out.println();
}
} else {
System.out.printf("No optimal solution found !");
}
}
public static void main(String[] args) throws IOException {
EmployeeSchedulingProblem esp = new EmployeeSchedulingProblem();
esp.orToolssolve();
}
}