《数据结构、算法与应用 —— C++语言描述》学习笔记 — 队列 —— 应用 —— 工厂仿真(二)
一、数据统计与展示
1、声明
#pragma once
#include "../data/machine/CLS_Machine.h"
#include "../data/machine/CLS_MachineObserver.h"
#include <iostream>
#include <map>
#include <vector>
class CLS_Statistics : public CLS_MachineObserver
{
public:
CLS_Statistics();
void outputTimeTable(std::ostream& os);
void outputMachinesWaitingTime(std::ostream& os);
void outputJobsTime(std::ostream& os);
void outputJobQueueTimeTable(std::ostream& os);
virtual void updateStatus(CLS_Machine* _pMachine) override;
virtual void updateQueue(CLS_Machine* _pMachine, CLS_Job* _pJob) override;
private:
std::map<int, std::vector<std::pair<CLS_Machine::EnumMachineStatus, int>>> m_mapExecuteTimeTable;
std::map<int, int> m_mapMachinesWaitingTime;
std::map<int, std::pair<int, int>> m_mapJobsTime;
std::map<int, std::vector<std::string>> m_mapJobQueueTimeTable;
std::map<int, std::vector<std::pair<CLS_Job*, int>>> m_mapAddingJobBeforeStatusChange;
void updateTimeTable(CLS_Machine* _pMachine);
void updateMachinesWaitingTime(CLS_Machine* _pMachine);
void updateJobsTime(CLS_Machine* _pMachine);
void updateJobqueueTimeTable(CLS_Machine* _pMachine);
};
2、实现
#include "CLS_Statistics.h"
#include "../data/CLS_DataManager.h"
#include <algorithm>
#include <string>
using namespace std;
const int CONST_JOBID_NONE = -1;
const string CONST_MACHINESTATUS_IDLE = "I";
const string CONST_MACHINESTATUS_CONVERT = "C";
const string CONST_MACHINESTATUS_EXECUTE = "E";
const string CONST_MACHINENAME_PREFIX = "M";
const string CONST_OUTPUTQUEUE_DELIMITER = ",";
CLS_Statistics::CLS_Statistics()
{
auto& machines = CLS_DataManager::getInstance().getMachines();
auto initMachineFunc = [this](CLS_Machine& _machine)
{
m_mapExecuteTimeTable[_machine.getMachineId()] = vector<pair<CLS_Machine::EnumMachineStatus, int>>(); _machine.addWatch(this);
m_mapMachinesWaitingTime[_machine.getMachineId()] = 0;
m_mapAddingJobBeforeStatusChange[_machine.getMachineId()] = vector<std::pair<CLS_Job*, int>>();
m_mapJobQueueTimeTable[_machine.getMachineId()] = vector<string>();
};
for_each(machines.begin(), machines.end(), initMachineFunc);
auto& jobs = CLS_DataManager::getInstance().getJobs();
for_each(jobs.begin(), jobs.end(), [this](CLS_Job& _job) { m_mapJobsTime[_job.getJobId()] = make_pair(0, 0); });
}
void CLS_Statistics::outputTimeTable(std::ostream& os)
{
int iTotalTime = 0;
for (auto& iterExecuteTimeTable : m_mapExecuteTimeTable)
{
if (iterExecuteTimeTable.second.size() > iTotalTime)
{
iTotalTime = iterExecuteTimeTable.second.size();
}
}
os << "时间/机器编号\t";
auto& machines = CLS_DataManager::getInstance().getMachines();
vector<decltype(m_mapExecuteTimeTable.begin())> vecIterExecuteTimeTable;
for (auto& machine : machines)
{
vecIterExecuteTimeTable.push_back(m_mapExecuteTimeTable.find(machine.getMachineId()));
os << machine.getMachineId() << "\t";
}
os << endl;
for (int time = 0; time < iTotalTime; ++time)
{
os << "\t" << time + 1 << "\t\t\t";
for (auto& iterExecuteTimeTable : vecIterExecuteTimeTable)
{
string strStatus;
string strJob;
if (iterExecuteTimeTable->second.size() <= time)
{
strStatus = CONST_MACHINESTATUS_IDLE;
strJob = "";
}
else
{
switch (iterExecuteTimeTable->second.at(time).first)
{
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Idle:
{
strStatus = CONST_MACHINESTATUS_IDLE;
break;
}
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Convert:
{
strStatus = CONST_MACHINESTATUS_CONVERT;
break;
}
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Execute:
{
strStatus = CONST_MACHINESTATUS_EXECUTE;
break;
}
default:
{
break;
}
}
strJob = iterExecuteTimeTable->second.at(time).second != CONST_JOBID_NONE ? to_string(int(iterExecuteTimeTable->second.at(time).second)) : "";
}
os << strStatus << strJob << "\t";
}
os << endl;
}
}
void CLS_Statistics::outputMachinesWaitingTime(std::ostream& os)
{
os << "机器编号\t等待时间" << endl;
auto outputFunc = [&os](pair<const int, int>& _pairMachinesWaitingTime) {os << "\t" << CONST_MACHINENAME_PREFIX << _pairMachinesWaitingTime.first << "\t\t\t" << _pairMachinesWaitingTime.second << endl; };
for_each(m_mapMachinesWaitingTime.begin(), m_mapMachinesWaitingTime.end(), outputFunc);
}
void CLS_Statistics::outputJobsTime(std::ostream& os)
{
os << "任务编号\t完成时间\t等待时间" << endl;
auto outputFunc = [&os](pair<const int, pair<int, int>>& _pairJobsTime)
{
os << "\t" << _pairJobsTime.first
<< "\t\t\t" << _pairJobsTime.second.second
<< "\t\t\t" << _pairJobsTime.second.second - _pairJobsTime.second.first
<< endl; };
for_each(m_mapJobsTime.begin(), m_mapJobsTime.end(), outputFunc);
}
void CLS_Statistics::outputJobQueueTimeTable(std::ostream& os)
{
int iTotalTime = 0;
for (auto& iterMapExecuteTimeTable : m_mapExecuteTimeTable)
{
if (iterMapExecuteTimeTable.second.size() > iTotalTime)
{
iTotalTime = iterMapExecuteTimeTable.second.size();
}
}
os << "时间/机器编号\t";
auto& machines = CLS_DataManager::getInstance().getMachines();
vector<decltype(m_mapJobQueueTimeTable.begin())> vecIterExecuteTimeTable;
for (auto& machine : machines)
{
vecIterExecuteTimeTable.push_back(m_mapJobQueueTimeTable.find(machine.getMachineId()));
os << machine.getMachineId() << "\t";
}
os << endl;
for (int i = 0; i < iTotalTime; ++i)
{
os << "\t" << i + 1 << "\t\t\t";
for (auto& iterExecuteTimeTable : vecIterExecuteTimeTable)
{
string strQueueOutput;
if (iterExecuteTimeTable->second.size() > i)
{
strQueueOutput = iterExecuteTimeTable->second.at(i);
}
os << strQueueOutput << "\t";
}
os << endl;
}
}
void CLS_Statistics::updateStatus(CLS_Machine* _pMachine)
{
if (_pMachine == nullptr)
{
return;
}
updateTimeTable(_pMachine);
updateMachinesWaitingTime(_pMachine);
updateJobsTime(_pMachine);
updateJobqueueTimeTable(_pMachine);
}
void CLS_Statistics::updateQueue(CLS_Machine* _pMachine, CLS_Job* _pJob)
{
int iDurationTime = _pMachine->getDurationTime();
m_mapAddingJobBeforeStatusChange[_pMachine->getMachineId()].push_back(make_pair(_pJob, iDurationTime < 0 ? 0 : iDurationTime));
}
void CLS_Statistics::updateTimeTable(CLS_Machine* _pMachine)
{
CLS_Machine::EnumMachineStatus iStatus;
switch (_pMachine->getState())
{
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Idled:
{
iStatus = CLS_Machine::EnumMachineStatus::enum_MachineStatus_Idle;
break;
}
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Converted:
{
iStatus = CLS_Machine::EnumMachineStatus::enum_MachineStatus_Convert;
break;
}
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Executed:
{
iStatus = CLS_Machine::EnumMachineStatus::enum_MachineStatus_Execute;
break;
}
default:
{
break;
}
}
auto iterExecuteTimeTable = m_mapExecuteTimeTable.find(_pMachine->getMachineId());
for (int i = 0; i < _pMachine->getDurationTime(); ++i)
{
iterExecuteTimeTable->second.push_back(make_pair(iStatus, _pMachine->getCurrentJob() ? _pMachine->getCurrentJob()->getJobId() : CONST_JOBID_NONE));
}
}
void CLS_Statistics::updateMachinesWaitingTime(CLS_Machine* _pMachine)
{
switch (_pMachine->getState())
{
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Converted:
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Executed:
{
int iTotalWaitTime = _pMachine->getJobQueue().size() * _pMachine->getDurationTime();
auto iter = m_mapAddingJobBeforeStatusChange.find(_pMachine->getMachineId());
for (auto& pairAddingJob : iter->second)
{
if (pairAddingJob.first != _pMachine->getCurrentJob())
{
iTotalWaitTime -= pairAddingJob.second;
}
}
m_mapMachinesWaitingTime[_pMachine->getMachineId()] += iTotalWaitTime;
break;
}
default:
{
break;
}
}
}
void CLS_Statistics::updateJobsTime(CLS_Machine* _pMachine)
{
switch (_pMachine->getState())
{
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Executed:
{
m_mapJobsTime[_pMachine->getCurrentJob()->getJobId()].first += _pMachine->getDurationTime();
auto nextTask = _pMachine->getCurrentJob()->getNextTask();
if (!nextTask.has_value())
{
m_mapJobsTime[_pMachine->getCurrentJob()->getJobId()].second = m_mapExecuteTimeTable.find(_pMachine->getMachineId())->second.size();
}
break;
}
default:
{
break;
}
}
}
void CLS_Statistics::updateJobqueueTimeTable(CLS_Machine* _pMachine)
{
switch (_pMachine->getState())
{
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Idle:
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Execute:
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Convert:
{
auto jobQueue = _pMachine->getJobQueue();
string strQueue;
while (!jobQueue.empty())
{
strQueue = strQueue + to_string(jobQueue.front()->getJobId()) + ",";
jobQueue.pop();
}
m_mapJobQueueTimeTable[_pMachine->getMachineId()].push_back(strQueue);
break;
}
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Idled:
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Executed:
case CLS_Machine::EnumMachineStatus::enum_MachineStatus_Converted:
{
auto iterJob = m_mapJobQueueTimeTable.find(_pMachine->getMachineId());
string strLastQueue = "";
if (!iterJob->second.empty())
{
strLastQueue = iterJob->second.back();
m_mapJobQueueTimeTable[_pMachine->getMachineId()].pop_back();
}
auto iterAddingJobBeforeStatusChange = m_mapAddingJobBeforeStatusChange.find(_pMachine->getMachineId());
int iLastAddJobTime = 0;
int iLastDurationTime = 0;
for (auto& jobPtrAndAddTime : iterAddingJobBeforeStatusChange->second)
{
if (jobPtrAndAddTime.second == iLastAddJobTime)
{
strLastQueue += to_string(jobPtrAndAddTime.first->getJobId()) + CONST_OUTPUTQUEUE_DELIMITER;
}
else
{
iLastAddJobTime = jobPtrAndAddTime.second;
for (; iLastDurationTime < jobPtrAndAddTime.second; ++iLastDurationTime)
{
m_mapJobQueueTimeTable[_pMachine->getMachineId()].push_back(strLastQueue.substr(0, strLastQueue.size() - 1));
}
strLastQueue += to_string(jobPtrAndAddTime.first->getJobId()) + ",";
}
}
for (; iLastDurationTime < _pMachine->getDurationTime(); ++iLastDurationTime)
{
m_mapJobQueueTimeTable[_pMachine->getMachineId()].push_back(strLastQueue.substr(0, strLastQueue.size() - 1));
}
iterAddingJobBeforeStatusChange->second.clear();
break;
}
default:
{
break;
}
}
}
二、测试代码
1、实现
#include ".\src\\service\CLS_DispatchManager.h"
#include ".\src\data\CLS_DataManager.h"
#include ".\src\service\CLS_Statistics.h"
#include <fstream>
#include <algorithm>
using namespace std;
void test()
{
ifstream ifs("input.txt");
ofstream ofs("output.txt");
int iJobNum;
int iMachineNum;
ifs >> iMachineNum >> iJobNum;
int iMachineId;
int iChangeTime;
for (int i = 0; i < iMachineNum; ++i)
{
ifs >> iMachineId;
ifs >> iChangeTime;
CLS_DataManager::getInstance().getMachines().push_back(CLS_Machine(iChangeTime, iMachineId++));
}
int iJobId;
int iTaskNum;
for (int i = 0; i < iJobNum; ++i)
{
ifs >> iJobId;
ifs >> iTaskNum;
CLS_Job job(iJobId);
int iMachineId;
int iTaskTime;
for (int j = 0; j < iTaskNum; ++j)
{
ifs >> iMachineId;
ifs >> iTaskTime;
job.addTask(iMachineId, iTaskTime);
}
CLS_DataManager::getInstance().addJob(job);
}
CLS_Statistics statistics;
CLS_DispatchManager dispatcher;
dispatcher.executeLoop();
statistics.outputTimeTable(ofs);
statistics.outputMachinesWaitingTime(ofs);
statistics.outputJobsTime(ofs);
statistics.outputJobQueueTimeTable(ofs);
}
2、测试文件
3 6
1 2
2 0
3 1
1 3
1 2 2 4 3 3
2 2
2 2 1 4
3 4
3 6 2 1 3 2 1 3
4 2
1 3 2 4
5 1
1 2
6 5
2 2 1 2 3 3 1 1 2 3
3、输出
下列输出没有打印各自展示的是哪部分数据(经过了手动对齐),从上到下依次为各机器状态时间表,各机器等待时长、各任务完成时间及等待时间、各机器等待队列时间表:
时间/机器编号 1 2 3
1 I I I
2 E1 E2 E3
3 E1 E2 E3
4 C E6 E3
5 C E6 E3
6 E4 E1 E3
7 E4 E1 E3
8 E4 E1 C
9 C E1 I
10 C E3 E1
11 E5 E4 E1
12 E5 E4 E1
13 C E4 C
14 C E4 E3
15 E2 I E3
16 E2 I C
17 E2 I I
18 E2 I I
19 C I I
20 C I I
21 E6 I I
22 E6 I I
23 C I E6
24 C I E6
25 E3 I E6
26 E3 I C
27 E3 I I
28 C I I
29 C I I
30 E6 I I
31 C E6 I
32 C E6 I
33 I E6 I
机器编号 等待时间
M1 52
M2 8
M3 3
任务编号 完成时间 等待时间
1 12 3
2 18 12
3 27 15
4 14 7
5 12 10
6 33 22
时间/机器编号 1 2 3
1 1,4,5 2,6 3
2 4,5 6
3 4,5 6
4 4,5,2 1
5 4,5,2 1
6 5,2,6
7 5,2,6
8 5,2,6 3
9 5,2,6 3,4
10 5,2,6 4
11 2,6 3
12 2,6 3
13 2,6 3
14 2,6
15 6
16 6,3
17 6,3
18 6,3
19 6,3
20 6,3
21 3
22 3
23 3
24 3
25
26 6
27 6
28 6
29 6
30
31
32
33
三、总结
这个设计的结构还是比较明确的,每个类都有各自的职责。但是,问题1 — 对于 CLS_Statistics 来说,它要统计的数据过于多,导致代码臃肿且相互耦合性过高。例如,我们这里要求 updateMachinesWaitingTime 和 updateJobqueueTimeTable 两个函数的调用顺序是一定的,因为它们使用了相同的数据,而该数据在被它们使用完后才能清理(可以选择将该数据提取到外面进行删除)。除此之外,其实每个需要统计的数据都可以单独成一个类,一开始我并没想统计这么多数据,所以疏忽了这部分设计。
如果想把它们各自独立,还有一个很严重的问题:问题2,没有独立的计时模块。不难发现,我们这里所有的计时功能都是通过时间表来统计的。这样的即使很不完善,也不方便我们将各模块独立。计时功能应该单独成一个模块,需要事件的对象去该模块中取时间即可。一开始,我的设计是按照书中的事件驱动方式来的,完全在一个函数中模拟时间的流动。后来发现这完全没有利用C++面向对象和封装的特性,因此才重新设计了一版。但是,其中还是残留了一些面向过程编程的痕迹。
还有一个问题是:问题3,使用串行的模式模拟并行过程,业务逻辑较为复杂。这里最说的就是队列时间表的输出。我们需要监控所有的状态,而不是像其它功能一样监控需要的2-3个状态即可。而且我们要了然它们是如何相互关联的。幸运也是很重要的一点是,我们这里确定了状态转换过程中机器对象内部数据的处理原则。这在一定程度上简化了设计。
最后一个小问题是:问题4,输入模块也可以独立。这里,我们手动在外面实现了输入和数据初始化功能。这部分功能可以独立成为工程中的一个模块。其要求用户传入按照固定的模式(json等)写成的配置文件进行数据初始化。这样可以减少用户的输入和需要支持的功能。
总的来说,这个工厂仿真应用确实让我们练习了观察者模式。但是在设计和模块化的道路上还有很远需要走。
|