Search code examples
c++data-structures

How can I develop a class to return unique numbers in the specified range?


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.


Solution

  • 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.