Project-2 电话转接中心
工程题目:
电话转接中心是一家负责接听和转发电话查询的公司。公司内包含三种级别的业务员:操作员,监督员和指导员。每个打进转接中心的电话查询都将根据其级别分配给相应级别的业务员进行处理,并优先分配给同一级别内序号靠前的业务员。每个业务员同一时间只能处理一个电话查询。每个业务员有一个状态域来表示其是否正在处理电话查询。
假设现在有X个操作员(1, …, X),Y个监督员(1, …, Y) ,Z个指导员(1, …, Z) 。
你需要完成任务分配工作并输出相应信息。
限制条件及假设:
- 业务员对应的级别为:操作员(0),监督员(1),指导员(2)
- 如果当前没有空闲的操作员,或被分配给的操作员当前无法处理电话查询,则电话将被分配给监督员。
- 如果当前没有空闲的监督员,或被分配给的监督员当前无法处理电话查询,则电话将被分配给指导员。
- 指导员可以处理所有电话查询
- 如果无人处理当前电话查询,则程序终止。
- 假设所有输入都是有效输入。
输入:
-
p第一行输入包含三个整数,即不同级别业务员的数量:X>=1, Y, Z<=100。
-
p第二行输入包含一个整数K,即电话查询的个数。
-
p接下来的K行输入按时间顺序给出电话信息。
-
p每行电话信息的格式如下:
<电话序号> <电话级别> <打入时间> <期望处理完成时间>
输出:
-
p输出K行数据,每行格式如下:
<业务员编号> 处理了 <电话序号>, <电话级别> <实际处理完成时间>
-
p如果发生无人处理电话的情况,则输出:
No agent can answer, stop
思考:
- 我们假设如果某个电话查询无人处理则程序终止,但有没有另一种解决办法?比如使用一个队列来保存所有无人处理的电话查询,等待有业务员进行处理。
- 我们目前使用迭代的方式决定将电话查询分配给哪个业务员,这将导致序号靠前的业务员会处理更多的电话,你打算如何解决这个问题?
解题思路:
-
构建Call类,将其用队列形式存在,用于存储所有电话结点
-
Call类成员变量:队列首结点指针
-
构建Node类,将其作为Call类队列的结点,用于存储每个电话结点信息
-
Node类成员变量:电话序号、电话级别、打入时间、期望处理完成时间、下一结点指针
-
构建Agent类,将其作为每个业务员类,其中包含了业务员的不同属性
-
Agent类成员变量:工作状态、工作开始时间、工作结束的时间、工作的总时间
-
构建CallCenter类,将其作为一个电话转接中心
-
CallCenter类成员变量:三种级别的业务员个数、待处理的电话队列、三种级别的业务员
-
主函数内:
创建Call和CallCenter类对象进行测试
利用PrintInt函数进行规范输入,如果输入不符合规范则抛出异常
如果程序运行错误,利用try-catch语句捕获错误信息内容并输出
如果程序运行成功,输出分配结果
创新点:
-
根据成员函数大小适当使用在类内和类外进行定义,提高编译效率。
-
编写PrintInt函数进行规范输入,其中包括:
检查输入的类型是否为符合题目规定的类型。
检查输入的数据是否满足题目规定的范围。
-
利用try-catch结构对输入类型的异常进行捕获并输入提示,错误效果图如下:
-4 Error: 输入数据范围错误! D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 15056)已退出,代码为 1。 按任意键关闭此窗口. . .
2.1 Error: 输入数据类型错误! D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 8340)已退出,代码为 1。 按任意键关闭此窗口. . .
-
针对思考1给出的解决方案:
如果出现没有业务员解决的时候,进行等待,直到出现符合的业务员处理该业务。
原示例结果:
4 2 1 8 1 0 0 30 2 1 5 50 3 0 8 30 4 0 12 20 5 0 15 25 6 0 18 20 7 1 20 30 8 0 25 30 Operator 1 process call 1, call level:0, end_t:30 supervisor 1 process call 2, call level:1, end_t:55 Operator 2 process call 3, call level:0, end_t:38 Operator 3 process call 4, call level:0, end_t:32 Operator 4 process call 5, call level:0, end_t:40 supervisor 2 process call 6, call level:0, end_t:38 Director 1 process call 7, call level:1, end_t:50 No agent can answer, stop D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 22932)已退出,代码为 0。 按任意键关闭此窗口. . .
改进之后的处理结果:
4 2 1 8 1 0 0 30 2 1 5 50 3 0 8 30 4 0 12 20 5 0 15 25 6 0 18 20 7 1 20 30 8 0 25 30 Operator 1 process call 1, call level:0, end_t:30 supervisor 1 process call 2, call level:1, end_t:55 Operator 2 process call 3, call level:0, end_t:38 Operator 3 process call 4, call level:0, end_t:32 Operator 4 process call 5, call level:0, end_t:40 supervisor 2 process call 6, call level:0, end_t:38 Director 1 process call 7, call level:1, end_t:50 Operator 1 process call 8, call level:0, end_t:60 D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 23740)已退出,代码为 0。 按任意键关闭此窗口. . .
-
针对思考2给出的解决方案:
为了合理分配,给Agent类加上成员变量total_time来计算该业务员总工作时长,每次分配的时候如果同时有多个空闲的业务员,选取同一级别内工作时长最短的进行工作。(不同级别由于工作性质的差异,根据实际情况而言,比较不同级别的工作时长合理性低,故只采用同一级别进行比较)
我们设置数据进行验证。
原方案结果:
3 1 1 5 1 0 5 5 2 0 12 5 3 0 20 5 4 0 34 5 5 0 50 5 Operator 1 process call 1, call level:0, end_t:10 Operator 1 process call 2, call level:0, end_t:17 Operator 1 process call 3, call level:0, end_t:25 Operator 1 process call 4, call level:0, end_t:39 Operator 1 process call 5, call level:0, end_t:55 D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 12260)已退出,代码为 0。 按任意键关闭此窗口. . .
改进后方案结果:
3 1 1 5 1 0 5 5 2 0 12 5 3 0 20 5 4 0 34 5 5 0 50 5 Operator 1 process call 1, call level:0, end_t:10 Operator 2 process call 2, call level:0, end_t:17 Operator 3 process call 3, call level:0, end_t:25 Operator 1 process call 4, call level:0, end_t:39 Operator 2 process call 5, call level:0, end_t:55 D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 17432)已退出,代码为 0。 按任意键关闭此窗口. . .
代码及注释:
CallCenter.h
#pragma once
#include<iostream>
using namespace std;
/*
创建Node类作为Call类的结点
用于存储每个电话结点的信息
*/
class Node {
public:
Node() {}
Node(int i, int l, int s, int t) {
index = i;
level = l;
start_time = s;
total_time = t;
next_node = 0;
}
//电话序号、电话级别、打入时间、期望处理完成时间
int index;
int level;
int start_time;
int total_time;
// 指向下一个结点
Node* next_node;
};
/*
创建Call类,本质为一个队列类
用于存储一个电话队列
*/
class Call {
public:
Call() { first_node = 0; }
// 判断队列是否为空
bool IsEmpty()const;
// 求队列长度
int Length()const;
// 加入新的队列结点
void Push(int index, int l, int s, int t);
// 删除队列结点
void Pop();
// 获取队列首元素的开始时间信息
int GetStartTime()const {
return first_node->start_time;
}
// 获取队列首元素的期望完成时间信息
int GetTotalTime()const {
return first_node->total_time;
}
// 获取队列首元素的电话级别
int GetLevel()const {
return first_node->level;
}
// 获取队列首元素的电话序号
int GetIndex()const { return first_node->index; }
private:
Node* first_node;
};
/*
创建Agent类表示业务员的属性和状态
*/
class Agent {
public:
Agent() {
state = false;
work_time = 0;
start_time = 0;
end_time = 0;
}
// 设置业务员的属性和状态
void SetData(bool f, int s, int t) {
state = f;
start_time = s;
end_time = t + s;
work_time += t;
}
void SetData() { state = 0; }
// 获取业务员当前工作状态
bool GetState()const { return state; }
// 获取业务员开始工作的时间
int GetEndTime()const { return end_time; }
// 获取业务员结束工作的时间
int GetWorkTime()const { return work_time; }
private:
// 工作状态,0表示空闲,1表示在工作
bool state;
// 工作开始时间
int start_time;
// 工作结束的时间
int end_time;
// 工作的总时间
int work_time;
};
/*
创建CallCenter类表示一个电话转接中心
*/
class CallCenter {
public:
CallCenter(){}
CallCenter(int f, int s, int t);
// 获取要处理的电话队列
void SetCallQueue(Call* call) {
call_queue = call;
}
/*
找到当前级别下一个空闲的业务员的编号,0表示不存在。
参数:l -- 查找的业务员级别(0或1或2)
*/
int LevelNotBusy(int l)const;
/*
找到当前级别下所有空闲的业务员中,工作总时长最少的一个业务员的编号,0表示不存在。
参数:l -- 查找的业务员级别(0或1或2)
*/
int ImproveLevelNotBusy(int l)const;
// 设置业务员属性
void SetLevel(int l, int number, int s, int e);
// 设置业务员工作状态
void SetState(int now_time);
// 展示最后的处理结果
void ShowMessage(int now_level, int number, int index, int pre_level, int end_time);
// 改进前的处理方式
void Run();
// 改进后的处理方式
void ImproveRun();
private:
// 三种级别的业务员个数
int agent_num[3];
// 待处理的电话队列
Call* call_queue;
// 三种级别的业务员
Agent* agent_level[3];
};
CallCenter.cpp
#include"Callcenter.h"
bool Call::IsEmpty()const {
return first_node == 0;
}
int Call::Length()const {
int len = 0;
for (Node* current = first_node; current; current = current->next_node)
len++;
return len;
}
void Call::Push(int i, int l, int s, int t) {
Node* current = first_node;
if (current) {
for (current; current->next_node; current = current->next_node);
Node* p = new Node(i, l, s, t);
p->next_node = 0;
current->next_node = p;
}
else {
Node* p = new Node(i, l, s, t);
p->next_node = 0;
first_node = p;
}
}
void Call::Pop() {
if (IsEmpty())
return;
Node* current = first_node;
if (Length() == 1) {
first_node = 0;
delete current;
}
else {
first_node = current->next_node;
delete current;
}
}
CallCenter::CallCenter(int f, int s, int t) {
call_queue = 0;
agent_num[0] = f;
agent_num[1] = s;
agent_num[2] = t;
agent_level[0] = new Agent[f];
agent_level[1] = new Agent[s];
agent_level[2] = new Agent[t];
}
int CallCenter::LevelNotBusy(int l)const {
for (int index = 0; index < agent_num[l]; index++) {
if (agent_level[l][index].GetState() == 0)
return index + 1;
}
return 0;
}
int CallCenter::ImproveLevelNotBusy(int l)const {
int suit_num = 0;
int min_work_time = INT_MAX;
for (int index = 0; index < agent_num[l]; index++) {
if (agent_level[l][index].GetState() == 0 && agent_level[l][index].GetWorkTime() < min_work_time) {
suit_num = index + 1;
min_work_time = agent_level[l][index].GetWorkTime();
}
}
return suit_num;
}
void CallCenter::SetLevel(int l, int number, int s, int t) {
agent_level[l][number - 1].SetData(1, s, t);
}
void CallCenter::SetState(int now_time) {
for (int type = 0; type < 3; type++) {
for (int index = 0; index < agent_num[type]; index++) {
if (agent_level[type][index].GetState() == 1 && agent_level[type][index].GetEndTime() <= now_time) {
agent_level[type][index].SetData();
}
}
}
}
void CallCenter::ShowMessage(int now_level, int number, int index, int pre_level, int end_time) {
if (now_level == 0) {
cout << "Operator " << number << " process call " << index;
cout << ", call level:" << pre_level << ", end_t:" << end_time << endl;
}
else if (now_level == 1) {
cout << "supervisor " << number << " process call " << index;
cout << ", call level:" << pre_level << ", end_t:" << end_time << endl;
}
else if (now_level == 2) {
cout << "Director " << number << " process call " << index;
cout << ", call level:" << pre_level << ", end_t:" << end_time << endl;
}
}
void CallCenter::Run() {
int call_len = call_queue->Length();
for (int index = 0; index < call_len; index++) {
this->SetState(call_queue->GetStartTime());
int pre_level = call_queue->GetLevel();
int number = LevelNotBusy(pre_level);
int now_level;
for (now_level = pre_level; now_level < 3; now_level++) {
number = LevelNotBusy(now_level);
if (number != 0)break;
}
if (number == 0) {
cout << "No agent can answer, stop" << endl;
return;
}
else {
this->SetLevel(now_level, number, call_queue->GetStartTime(), call_queue->GetTotalTime());
this->ShowMessage(now_level, number, call_queue->GetIndex(), pre_level, call_queue->GetTotalTime() + call_queue->GetStartTime());
}
call_queue->Pop();
}
}
void CallCenter::ImproveRun() {
int now_time = 0;
while (call_queue->Length()) {
if (now_time < call_queue->GetStartTime())
now_time = call_queue->GetStartTime();
this->SetState(now_time);
int pre_level = call_queue->GetLevel();
int number = ImproveLevelNotBusy(pre_level);
int now_level;
for (now_level = pre_level; now_level < 3; now_level++) {
number = ImproveLevelNotBusy(now_level);
if (number != 0)break;
}
if (number != 0) {
this->SetLevel(now_level, number, now_time, call_queue->GetTotalTime());
this->ShowMessage(now_level, number, call_queue->GetIndex(), pre_level, call_queue->GetTotalTime() + now_time);
call_queue->Pop();
}
now_time++;
}
}
main.cpp
#include<iostream>
#include"CallCenter.h"
using namespace std;
/*
输入函数,min和max限定输入数据的大小范围
如果输入数据不为int类型或者数据大小超过条件范围,则抛出异常
*/
void PrintInt(int &num, int min, int max) {
double m;
cin >> m;
num = static_cast<int>(m);
if (cin.fail()|| m!= num)
throw "输入数据类型错误!";
if (num < min || num > max)
throw "输入数据范围错误!";
}
int main() {
try {
// 输入三个不同级别业务员的数量
int level1_num, level2_num, level3_num;
PrintInt(level1_num, 1, INT_MAX);
PrintInt(level2_num, 0, 100);
PrintInt(level3_num, 0, 100);
// 创建一个Center类表示一个转接中心
CallCenter center(level1_num, level2_num, level3_num);
// 创建Call类表示将要处理的电话队列
Call call;
// 将call给center进行处理
center.SetCallQueue(&call);
// 输入电话数目
int call_num;
PrintInt(call_num, 0, INT_MAX);
for (int i = 1; i <= call_num; i++) {
// 输入电话序号、电话级别、打入时间、期望处理完成时间
int index, level_num, start_time, total_time;
PrintInt(index, 1, call_num);
PrintInt(level_num, 0, 2);
PrintInt(start_time, 0, INT_MAX);
PrintInt(total_time, 0, INT_MAX);
// 将电话信息放入call电话队列中
call.Push(i, level_num, start_time, total_time);
}
// 原始处理方式
center.Run();
// 改进后的处理方式
//center.ImproveRun();
return EXIT_SUCCESS;
}
catch (char const* str) {
// 输出错误信息提示
cout << "Error: " << endl;
cout << " " << str << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
程序运行结果分析:
原示例结果:
4 2 1
8
1 0 0 30
2 1 5 50
3 0 8 30
4 0 12 20
5 0 15 25
6 0 18 20
7 1 20 30
8 0 25 30
Operator 1 process call 1, call level:0, end_t:30
supervisor 1 process call 2, call level:1, end_t:55
Operator 2 process call 3, call level:0, end_t:38
Operator 3 process call 4, call level:0, end_t:32
Operator 4 process call 5, call level:0, end_t:40
supervisor 2 process call 6, call level:0, end_t:38
Director 1 process call 7, call level:1, end_t:50
No agent can answer, stop
D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 22932)已退出,代码为 0。
按任意键关闭此窗口. . .
改进之后结果如下:
4 2 1
8
1 0 0 30
2 1 5 50
3 0 8 30
4 0 12 20
5 0 15 25
6 0 18 20
7 1 20 30
8 0 25 30
Operator 1 process call 1, call level:0, end_t:30
supervisor 1 process call 2, call level:1, end_t:55
Operator 2 process call 3, call level:0, end_t:38
Operator 3 process call 4, call level:0, end_t:32
Operator 4 process call 5, call level:0, end_t:40
supervisor 2 process call 6, call level:0, end_t:38
Director 1 process call 7, call level:1, end_t:50
Operator 1 process call 8, call level:0, end_t:60
D:\Private\Files\C++\高级语言程序设计2-2\Project2\Debug\Project2.exe (进程 23740)已退出,代码为 0。
按任意键关闭此窗口. . .