Created
March 9, 2025 11:03
-
-
Save htlin222/1752307939a71c88d55dfe64668b3449 to your computer and use it in GitHub Desktop.
排班小精靈 (Scheduling Wizard) - Python Version
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
""" | |
排班小精靈 (Scheduling Wizard) - Python Version | |
输入格式 (Input Format): | |
--------------------- | |
1. 第一行: 日数,起始星期,参与排班人数 | |
First line: number_of_days,starting_weekday,number_of_staff | |
2. 第二行: 国定假日1,国定假日2,... | |
Second line: national_holiday1,national_holiday2,... | |
3. 后续每行: 员工工作日排班数,员工假日排班数,员工固定不排班日期1,员工固定不排班日期2,... | |
Following lines: workday_shifts,holiday_shifts,fixed_day_off1,fixed_day_off2,... | |
示例 (Example): | |
-------------- | |
31,1,3 | |
14,21 | |
20,4,5,12,19,26 | |
20,4,6,13,20,27 | |
20,4,7,14,21,28 | |
这表示这个月有31天,从星期一(1)开始,有3个人参与排班。 | |
14号和21号是国定假日。 | |
第一个员工排20个工作日,4个假日,固定不排班日期是5,12,19,26。 | |
以此类推。 | |
""" | |
# 初始化全局变量 | |
i, k, t, day, start, mem, max_score, score, today, check = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
balance = [0] * 10 | |
w = [0] * 10 | |
h = [0] * 10 | |
b = [0] * 10 | |
lim = [0] * 10 | |
shift = [0] * 32 | |
holi = [0] * 36 | |
best = [0] * 32 | |
order = [0] * 32 | |
no = [[0 for _ in range(36)] for _ in range(10)] | |
want = [[0 for _ in range(32)] for _ in range(10)] | |
sum_val = [[0 for _ in range(32)] for _ in range(10)] # 计算从1开始 | |
count = [[0 for _ in range(10)] for _ in range(10)] # 1-7为星期,8为工作日,9为假日 | |
penalty = [0, 2, 2, 2, 32, 64, 128, 128] # 惩罚值按照工作天 | |
def maxof(x, y): | |
return x if x > y else y | |
def print_result(r): # 打印排班 | |
print("") | |
for k in range(1, start): | |
print(" ", end="") | |
for k in range(1, day + 1): | |
print(f"{r[k]:2d} ", end="") | |
if (k + start - 1) % 7 == 0 and k < day: | |
print("") | |
print(f" \nScore={max_score}") | |
def main(): | |
global i, k, t, day, start, mem, max_score, score, today, check | |
global balance, w, h, b, lim, shift, holi, best, order, no, want, sum_val, count | |
try: | |
with open("input.txt", "r") as file: | |
# 输入:月份天数、起始星期、参与排班人数 | |
line = file.readline().strip().split(',') | |
day, start, mem = int(line[0]), int(line[1]), int(line[2]) | |
# 输入:国定假日 | |
line = file.readline().strip().split(',') | |
for item in line: | |
if item: | |
holi[int(item)] = 1 | |
# 输入:工作日排班天数、假日排班天数、固定不排班日期 | |
for i in range(1, mem + 1): | |
line = file.readline().strip().split(',') | |
w[i], h[i] = int(line[0]), int(line[1]) | |
for j in range(2, len(line)): | |
if line[j]: | |
no[i][int(line[j])] = 1 | |
except FileNotFoundError: | |
print("\n请有 input.txt,请见 README.txt") | |
input("输入任意字结束:") | |
return 0 | |
# 定义前置排班 | |
for i in range(1, mem + 1): | |
for k in range(3, day + 1): | |
if no[i][k] == 1 and (start + k - 2) % 7 > 4: | |
want[i][k - (start + k - 2) % 7 + 3] = 1 # 假日固定不排班 | |
if no[i][k] == 1 and holi[k] == 1: | |
want[i][k - 2] = 1 # 国定假日固定不排班 | |
if no[i][k] == 1 and no[i][k + 1] == 1: | |
want[i][k - 2] = 1 # 连续固定不排班 | |
for k in range(2, day + 1): | |
if (start + k - 2) % 7 > 4 or holi[i] == 1 or no[i][k] == 1: | |
want[i][k - 1] = 0 # 没有人会想在假前一天排班 | |
for k in range(1, day + 1): | |
if no[i][k] == 1: | |
want[i][k] = 0 # 没办法排班就不会想排班 | |
# 计算各日期好坏 | |
value = [[0 for _ in range(32)] for _ in range(10)] | |
for i in range(1, day + 1): | |
if holi[34] > 0 and holi[i] > 0: | |
value[0][i] += holi[34] # 有连假才好 | |
elif holi[i + 1] > 0: | |
value[0][i] -= 1 # 前一天排班差 | |
elif (start + i - 1) % 7 == 0: | |
value[0][i] += 1 # 排星期日有 day off | |
elif (start + i - 1) % 7 == 4: | |
value[0][i] += 1 # 排星期四有 day off | |
elif (start + i - 1) % 7 > 4: | |
value[0][i] -= 1 # 排五六没有 day off | |
value[0][0] += value[0][i] # 好坏总和 | |
for i in range(1, mem + 1): | |
value[0][0] -= no[i][33] + no[i][34] + no[i][35] - 1 # 确认好坏平衡 | |
for i in range(1, mem + 1): | |
if no[i][33] == 0 and holi[33] == 0: | |
value[0][0] += 16384 | |
if value[0][0] < 0: | |
print("好坏平衡绝对无法完成!") | |
input("输入任意字结束:") | |
return 0 | |
# 计算工作日排班数量 | |
t = 0 | |
for i in range(1, day + 1): | |
if (start + i - 2) % 7 < 5 and holi[i] == 0: | |
t += 1 | |
for i in range(1, mem + 1): | |
t -= w[i] | |
# 计算假日排班数量 | |
k = 0 | |
for i in range(1, day + 1): | |
if (start + i - 2) % 7 > 4 or holi[i] == 1: | |
k += 1 | |
for i in range(1, mem + 1): | |
k -= h[i] | |
# 检查排班数量是否合理 | |
if t + k == 0 and t > 0: | |
print(f"假日排班数{t}天应改为工作日") | |
input("输入任意字结束:") | |
return 0 | |
if t + k == 0 and t < 0: | |
print(f"工作日排班数{k}天应改为假日") | |
input("输入任意字结束:") | |
return 0 | |
if t > 0: | |
print(f"\n工作日排班数少{t}天") | |
elif t < 0: | |
print(f"\n工作日排班数多{-t}天") | |
if k > 0: | |
print(f"\n假日排班数少{k}天") | |
elif k < 0: | |
print(f"\n假日排班数多{-k}天") | |
if t != 0 or k != 0: | |
input("输入任意字结束:") | |
return 0 | |
# 优先排假日 | |
t = 1 | |
for i in range(1, day + 1): | |
if (start + i - 2) % 7 > 4 or holi[i] == 1: | |
order[t] = i | |
t += 1 | |
check += 1 | |
# 记录相关好坏的工作日 | |
for i in range(1, day + 1): | |
if value[0][i] != 0 and (start + i - 2) % 7 < 5 and holi[i] == 0: | |
order[t] = i | |
t += 1 | |
check += 1 | |
# 不记录相关好坏的日期 | |
for i in range(1, day + 1): | |
if value[0][i] == 0 and (start + i - 2) % 7 < 5 and holi[i] == 0: | |
order[t] = i | |
t += 1 | |
# 重置计数 | |
for i in range(mem + 1): | |
for k in range(10): | |
count[i][k] = 0 | |
# 计算各人满分数理想下限,搜索法相同,递归法本身 | |
for t in range(1, mem + 1): | |
for k in range(day + 1): | |
shift[k] = 42 | |
sum_val[0][k] = 0 | |
max_score = 16384 | |
today = 1 | |
while today > 0: | |
while today <= day and today > 0: | |
if shift[today] == 42: | |
shift[today] = 0 | |
count[0][t] += 1 | |
elif shift[today] == t: | |
if holi[today] == 1 and (start + today - 2) % 7 < 5: | |
count[t][9] -= 1 | |
else: | |
count[t][((start + today - 2) % 7) // 5 + 8] -= 1 | |
count[t][(start + today - 2) % 7 + 1] -= 1 | |
balance[t] -= value[0][today] | |
shift[today] = 42 | |
today -= 1 | |
continue | |
else: | |
if holi[today] == 1 and (start + today - 2) % 7 < 5: | |
count[t][9] += 1 | |
else: | |
count[t][((start + today - 2) % 7) // 5 + 8] += 1 | |
count[0][t] -= 1 | |
count[t][(start + today - 2) % 7 + 1] += 1 | |
balance[t] += value[0][today] | |
shift[today] = t | |
if count[t][8] > w[t]: | |
continue | |
if count[t][9] > h[t]: | |
continue | |
if count[0][t] > day - w[t] - h[t]: | |
continue | |
if today == day: | |
if no[t][33] > 0 and balance[t] < no[t][33] + no[t][34] + no[t][35] - 1: | |
continue | |
elif holi[33] > 0 and balance[t] < -1: | |
continue | |
score = 0 | |
if shift[today] == t: | |
if shift[today] == shift[today - 1] and today > 1: | |
continue | |
if no[t][today] == 1: | |
continue | |
if t == shift[today - 2] and today > 2: | |
score += 256 | |
if t == shift[today - 3] and today > 3: | |
score += 1 | |
if count[t][(start + today - 2) % 7 + 1] > 1: | |
score += penalty[(start + today - 2) % 7 + 1] | |
if no[t][today + 1] == 1: | |
score += 16 | |
if max_score <= sum_val[0][today - 1] + score: | |
continue | |
sum_val[0][today] = sum_val[0][today - 1] + score | |
today += 1 | |
if today > day: | |
max_score = sum_val[0][day] | |
today = day | |
lim[t] = max_score # 各人满分数理想下限 | |
if lim[t] > 16383: | |
print(f"人员{t}排班绝对无法完成!") | |
input("输入任意字结束:") | |
return 0 | |
# 初始化排班搜索 | |
max_score = 16384 | |
today = 1 | |
# 排班预设 | |
for k in range(day + 1): | |
shift[k] = 42 | |
sum_val[0][k] = 0 | |
# 星期别排班数预设 | |
for i in range(mem + 1): | |
balance[i] = 0 | |
for k in range(10): | |
count[i][k] = 0 | |
# 搜索法本身 | |
while today > 0: | |
# 选择:进位还是退回开头 | |
while today <= day and today > 0: | |
if shift[order[today]] > 10: | |
# 选项一:前进位才排班的日期 | |
shift[order[today]] = 1 # 分配从人员编号开始排这一天 | |
# 计算国定假日 | |
if holi[order[today]] == 1 and (start + order[today] - 2) % 7 < 5: | |
count[shift[order[today]]][9] += 1 | |
else: | |
count[shift[order[today]]][((start + order[today] - 2) % 7) // 5 + 8] += 1 | |
# 计算星期 | |
count[shift[order[today]]][(start + order[today] - 2) % 7 + 1] += 1 | |
# 计算好坏变动 | |
balance[shift[order[today]]] += value[0][order[today]] | |
elif shift[order[today]] == mem: | |
# 选项二:所有人都已经排过 | |
if holi[order[today]] == 1 and (start + order[today] - 2) % 7 < 5: | |
count[shift[order[today]]][9] -= 1 | |
else: | |
count[shift[order[today]]][((start + order[today] - 2) % 7) // 5 + 8] -= 1 | |
count[shift[order[today]]][(start + order[today] - 2) % 7 + 1] -= 1 | |
balance[shift[order[today]]] -= value[0][order[today]] | |
shift[order[today]] = 42 # 当天排班预设 | |
today -= 1 # 退一步改排班 | |
continue | |
else: | |
# 选项三:还有人排班过 | |
if holi[order[today]] == 1 and (start + order[today] - 2) % 7 < 5: | |
# 计算国定假日,一加一减 | |
count[shift[order[today]]][9] -= 1 | |
count[shift[order[today]] + 1][9] += 1 | |
else: | |
# 计算非国定假日 | |
count[shift[order[today]]][((start + order[today] - 2) % 7) // 5 + 8] -= 1 | |
count[shift[order[today]] + 1][((start + order[today] - 2) % 7) // 5 + 8] += 1 | |
# 计算星期 | |
count[shift[order[today]]][(start + order[today] - 2) % 7 + 1] -= 1 | |
count[shift[order[today]] + 1][(start + order[today] - 2) % 7 + 1] += 1 | |
# 计算好坏变动 | |
balance[shift[order[today]]] -= value[0][order[today]] | |
balance[shift[order[today]] + 1] += value[0][order[today]] | |
shift[order[today]] += 1 # 换下一人员排这一天 | |
if today == 4: | |
print(f" {shift[order[1]]} {shift[order[2]]} {shift[order[3]]} {shift[order[4]]} ...") | |
# 排班检查 | |
if shift[order[today]] == shift[order[today - 1]] and order[today] > 1: | |
continue # QD | |
if shift[order[today]] == shift[order[today] + 1] and order[today] <= day - 1: | |
continue # QD | |
if count[shift[order[today]]][8] > w[shift[order[today]]]: | |
continue # 工作日排班数 | |
if count[shift[order[today]]][9] > h[shift[order[today]]]: | |
continue # 假日排班数 | |
if no[shift[order[today]]][order[today]] == 1: | |
continue # 固定不排班 | |
# 计分 | |
score = 0 | |
if shift[order[today]] == shift[order[today] - 2] and order[today] > 2: | |
score += 256 # QOD | |
if shift[order[today]] == shift[order[today] + 2] and order[today] <= day - 2: | |
score += 256 # QOD | |
if shift[order[today]] == shift[order[today] - 3] and order[today] > 3: | |
score += 1 # Q3D | |
if shift[order[today]] == shift[order[today] + 3] and order[today] <= day - 3: | |
score += 1 # Q3D | |
if count[shift[order[today]]][(start + order[today] - 2) % 7 + 1] > 1: | |
score += penalty[(start + order[today] - 2) % 7 + 1] | |
# 前置排班 | |
for i in range(1, mem + 1): | |
if want[i][order[today]] > 0 and shift[order[today]] != i: | |
score += 8 | |
# 固定不排班前一日 | |
if no[shift[order[today]]][order[today] + 1] == 1: | |
score += 16 | |
# 若惩罚已超上限,则不继续排 | |
if max_score <= sum_val[0][today - 1] + score: | |
continue | |
# 计算今日排班人员导致之惩罚 | |
t = sum_val[shift[order[today]]][today - 1] + score | |
# 加上非今日排班人员理想值 | |
for i in range(1, mem + 1): | |
if i != shift[order[today]]: | |
t += maxof(sum_val[i][today - 1], lim[i]) | |
# 检查好坏平衡 | |
if today == check: | |
for i in range(1, mem + 1): | |
if no[i][33] > 0 and balance[i] < no[i][33] + no[i][34] + no[i][35] - 1: | |
t += 16384 | |
elif holi[33] > 0 and balance[i] < -1: | |
t += 16384 | |
# 将以上列入准量,若预期惩罚超出上限,则不继续排 | |
if t >= max_score: | |
continue | |
# 复制各人员累积的惩罚 | |
for k in range(mem + 1): | |
sum_val[k][today] = sum_val[k][today - 1] | |
# 更新今日的惩罚 | |
sum_val[shift[order[today]]][today] += score | |
# 更新今日的惩罚总和 | |
sum_val[0][today] += score | |
# 经过上述严选后,才继续排下一天 | |
today += 1 | |
if today > day: | |
for k in range(1, day + 1): | |
best[k] = shift[k] # 复制最佳排班 | |
for k in range(1, 10): | |
b[k] = balance[k] # 复制最佳排班好坏 | |
max_score = t # 复制最佳惩罚 | |
print_result(shift) | |
today = day # 不惩罚已探索过的区域 | |
# 输出最佳排班 | |
print("\n-----------------------\n 一 二 三 四 五 六 日") | |
print_result(best) | |
# 计算星期别排班数 | |
for i in range(1, day + 1): | |
count[best[i]][(start + i - 2) % 7 + 1] += 1 | |
# 计算各种惩罚 | |
for i in range(1, mem + 1): | |
print(f"\n人员{i} {w[i]}工{h[i]}假({b[i]:+d}):", end="") | |
# QOD 排班 | |
t = 0 | |
for k in range(1, day): | |
if best[k] == i and best[k + 2] == i and k + 2 <= day: | |
t += 1 | |
if t > 0: | |
print(f"有{t}次QOD排班, ", end="") | |
t = 0 | |
# Q3D 排班 | |
for k in range(1, day): | |
if best[k] == i and best[k + 3] == i and k + 3 <= day: | |
t += 1 | |
if t > 0: | |
print(f"有{t}次Q3D排班, ", end="") | |
# 前置排班 | |
for k in range(1, day): | |
if best[k] == i and want[i][k] > 0: | |
print(f"前置排班({k}号), ", end="") | |
# 固定不排班前一天 | |
for k in range(1, day): | |
if best[k] == i and no[i][k + 1] == 1: | |
print(f"固定不排班前一天({k}号)排班, ", end="") | |
# 惩罚排班 | |
for k in range(1, 8): | |
if count[i][k] > 1: | |
print(f"W{k}惩罚排班, ", end="") | |
# 好坏作业(总结) | |
print("\n好坏讨论(总结):", end="") | |
if holi[33] > 0: | |
print("改善(-1) ", end="") | |
for i in range(1, mem + 1): | |
if no[i][33] > 0: | |
print(f"人员{i}(+{no[i][33] + no[i][34] + no[i][35] - 1}) ", end="") | |
print("\n\n输入任意字结束:", end="") | |
input() | |
return 0 | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment