I need to develop a class to return unique numbers in the specified range. For example, if the specified range is 1-4, calling GetNextNumber()
will work as follows:
MyClass obj = new MyClass();
obj->min = 1;
obj->max = 4;
obj->expirationTime = 30; //seconds
int nextNumber = 0;
nextNumber = obj->GetNextNumber(); // returns 1
nextNumber = obj->GetNextNumber(); // returns 2
nextNumber = obj->GetNextNumber(); // returns 3
nextNumber = obj->GetNextNumber(); // returns 4
nextNumber = obj->GetNextNumber(); // returns -1 or throws an exception, becuase all numbers from 1 to 4 have been used.
obj->ReleaseNumber(2); // now that number 2 has been released, it can be used again
nextNumber = obj->GetNextNumber(); // returns 2
nextNumber = obj->GetNextNumber(); // returns -1 or throws an exception, becuase all numbers from 1 to 4 have been used.
If we do not call ReleaseNumber()
on a number returned from GetNextNumber()
it will be released and returned by ReleaseNextExpiredNumber()
method.
// after 10 seconds:
int nextExpiredNumber = 0;
nextExpiredNumber = obj->ReleaseNextExpiredNumber(); // returns -1 or throws and exeption because nothing has been expired after 10 seconds
// after 30 seconds
nextExpiredNumber = obj->ReleaseNextExpiredNumber(); // returns 1, because "ReleaseNumber(1)" has not been called within 30 seconds.
// now that number 1 has been released, the `GetNextNumber()` method will return 1:
nextNumber = obj->GetNextNumber(); // returns 1
I am going to develop such class on my own, but before doing that, I want to make sure I am not reinventing the wheel. I also need this class to be thread-safe.
Dealing with numbers is just a matter of pushing and popping onto a container. I picked deque
since it's efficient at pop_front
and push_back
:
class MyClass {
std::deque<int> numbers;
public:
MyClass(int lo, int hi) {
numbers.resize(hi - lo + 1);
std::iota(numbers.begin(), numbers.end(), lo);
}
int getNextNumber() {
if (!numbers.empty()) {
int next = numbers[0];
numbers.pop_front();
return next;
}
else {
return -1;
}
}
void releaseNumber(int i) {
numbers.push_back(i);
}
};
Adding times on top of that means you just have another container of values with timestamps:
using timestamp = std::chrono::system_clock::time_point; // or whatever
std::vector<std::pair<int, timestamp>> used;
And making getNextNumber()
push onto that vector:
numbers.pop_front();
used.emplace_back(next, std::chrono::system_clock::now());
return next;
And releaseNumber()
erase from it:
auto it = std::find_if(used.begin(), used.end(),
[=](const std::pair<int, timestamp>& p){
return p.first == i;
});
used.erase(it);
With that, ReleaseNextExpiredNumber()
is just a matter of checking used.front()
: if it's expired, return releaseNumber()
, else return -1.