First the nitty picky stuff
Every container has a empty()
method. I would consider it bad practice to use size() > 0
rather than !empty()
, as it can have really performance implictations for lists and maps.
Please post complete code. You are missing any headers and also the dubious using namespace std;
That one you should really ommit in your code and start using proper namespaces.
So now to the problem at hand. I think your data structure is indeed badly choosen. The first thing to note is what is important.
a. The type of instruction
b. The number of remaining instructions
c. The time until a certain instruction is ready.
You are missing the third point, which complicates the code more than it should. So lets start with a data structure that fits the problem
struct Instruction {
Instruction(const char id)
: Identifier(id)
{}
const char Identifier;
size_t remainingUses = 1;
size_t nextPossibleTime = 0;
}
So I named it next possible time, because the current time since start is the same for every instruction. Now given a current time we can schedule an instruction
char scheduleInstruction(const size_t currentTime, const size_t coolDown) {
--remainingUses;
nextPossibleTime = currentTime + coolDown + 1;
return Identifier;
}
So when we schedule an instruction we decrement the number of remaining uses and set the next possible instruction time until the current time + the cool down period. Obviously we can put currentTime+coolDown+1
in the caller but that is a question of personal preference.
Now to check whether an Instruction can be scheduled we only have to compare currentTime
with nextPossibleTime for the instruction.
So now we have scheduled an instruction how do we move it withine a queue? Quite easy there are only 2 relevant quantities
1. nextPossibleTime
2. remainingUses
So lets define a comparison operator for that task as you did
inline bool
operator< (Instruction const& b) const {
return std::tie(nextPossibleTime, remainingUses) <
std::tie(b.nextPossibleTime, b.remainingUses);
}
By using std::tie
we automatically get the correct comparison of the two fields. Now consider a given container and we have scheduled an instruction, then we swap the elements until our scheduled instruction is not smaller than
another given instruction.
void rescheduleInstructions() {
auto lastInstruction = myInstructions.rbegin();
auto nextInstruction = std::next(lastInstruction, 1);
while(nextInstruction != myInstructions.rend() && (*lastInstruction< *nextInstruction) {
std::iter_swap(nextInstruction , lastInstruction);
++nextInstruction;
++lastInstruction;
}
}
EDIT:
So I made a mistake here. The problem becomes obvious when looking at the simple sequence
AAABC n=2
The A Instruction will be scheduled after B and C with the old code, leading to the sequence
A->B->C->A->Idle->A
However the optial sequence would be
A->B->A->C->A
The reason for the error is, that we only need go back at max coolDown
steps as after that point any instruction will be ready and we can sort from there with simply after the remainingUses
, which ensures that we always schedule the most important instruction first.
void rescheduleInstructions() {
auto lastInstruction = myInstructions.rbegin();
auto nextInstruction = std::next(lastInstruction, 1);
for (size_t step = 0; step < coolDown; ++step ) {
if (nextInstruction == myInstructions.rend()) {
break;
}
if (*nextInstruction < *lastInstruction) {
break;
}
std::iter_swap(nextInstruction , lastInstruction);
++nextInstruction;
++lastInstruction;
}
}
So lets wrap this up by creating a class for the instruction set
class InstructionSet {
InstructionSet (const std::string instructions, const size_t cooldown)
:coolDown(cooldown)
{
for (const char& instruction : instructions) {
auto pos = std::find(myInstructions.begin(), myInstructions.end(), [&instruction](const Instruction& i) {return i.Identifier == instruction; });
if (pos != myInstructions.end()) {
pos->AddInstruction();
} else {
myInstructions.emplace_back(instruction);
}
}
std::sort(myInstructions.rbegin(), myInstructions.rend());
}
std::string findOptimalSchedule();
private:
void rescheduleInstructions();
std::container<Instruction> myInstructions;
size_t currentTime = 1;
const size_t coolDown;
}
You could also go for sorted insertion into the vector, however i felt that it complicates the matter of finding and in my experience just inserting and sorting at the end is more performant.
Now for the optimal schedule we have to start with the last element and then go from there. If currentTime is not less than nextPossibleTime of the final instruction we can schedule it. Otherwise due to the ordering we know that there is no possible instruction and we have to go idle (I choose "1" for that). After an instruction has been scheduled, we check whether there are remaining uses and either remove it or reschedule.
std::string findOptimalSchedule() {
std::string schedule;
while(!myInstructions.empty()) {
auto nextInstruction = myInstructions.rbegin();
if(currentTime >= nextInstruction->nextPossibleTime) {
schedule.push_back(nextInstruction->scheduleInstruction(currentTime, coolDown));
if (nextInstruction->remainingUses == 0) {
myInstructions.erase(nextInstruction);
} else {
rescheduleInstructions();
}
} else {
schedule.push_back("1");
}
++currentTime;
}
return schedule;
}
Edit
To put all the code together and remove the unnecessary occurence of std::string although it
#include <algorithm>
#include <vector>
struct Instruction {
Instruction(const char id)
: Identifier(id)
{}
inline bool
operator< (Instruction const& b) const {
return std::tie(nextPossibleTime, remainingUses) <
std::tie(b.nextPossibleTime, b.remainingUses);
}
char scheduleInstruction(const size_t currentTime, const size_t coolDown) {
--remainingUses;
nextPossibleTime = currentTime + coolDown + 1;
return Identifier;
}
const char Identifier;
size_t remainingUses = 1;
size_t nextPossibleTime = 0;
}
class InstructionSet {
InstructionSet (const std::vector<char> instructions, const size_t cooldown)
:coolDown(cooldown)
{
for (const char& instruction : instructions) {
auto pos = std::find(myInstructions.begin(), myInstructions.end(), [&instruction](const Instruction& i) {return i.Identifier == instruction; });
if (pos != myInstructions.end()) {
pos->AddInstruction();
} else {
myInstructions.emplace_back(instruction);
}
}
std::sort(myInstructions.rbegin(), myInstructions.rend());
}
int findOptimalSchedule() {
std::vector<char> schedule;
while(!myInstructions.empty()) {
auto nextInstruction = myInstructions.rbegin();
if(currentTime >= nextInstruction->nextPossibleTime) {
schedule.push_back(nextInstruction->scheduleInstruction(currentTime, coolDown));
if (nextInstruction->remainingUses == 0) {
myInstructions.erase(nextInstruction);
} else {
rescheduleInstructions();
}
} else {
schedule.push_back('1');
}
++currentTime;
}
return static_cast<int>(schedule.size());
}
private:
void rescheduleInstructions() {
auto lastInstruction = myInstructions.rbegin();
auto nextInstruction = std::next(lastInstruction, 1);
for (size_t step = 0; step < coolDown; ++step ) {
if (nextInstruction == myInstructions.rend()) {
break;
}
if (*nextInstruction < *lastInstruction) {
break;
}
std::iter_swap(nextInstruction , lastInstruction);
++nextInstruction;
++lastInstruction;
}
}
std::vector<Instruction> myInstructions;
size_t currentTime = 1;
const size_t coolDown;
}