如何在指定范围内开发一个 class 到 return 唯一数字?

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

我需要制定一个class到return指定范围内的唯一号码。例如,如果指定范围是 1-4,调用 GetNextNumber() 将按如下方式工作:

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.

如果我们不对从 GetNextNumber() 编辑的号码 return 调用 ReleaseNumber(),它将被释放并通过 ReleaseNextExpiredNumber() 方法 return 编辑。

// 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

我打算自己开发这样的 class,但在此之前,我想确保我没有重新发明轮子。我还需要这个 class 是线程安全的。感谢您的任何建议!

处理数字只是推入和弹出容器的问题。我选择了 deque,因为它在 pop_frontpush_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);
    }
};

在此基础上添加时间意味着您只有另一个带有时间戳的值容器:

using timestamp = std::chrono::system_clock::time_point; // or whatever
std::vector<std::pair<int, timestamp>> used;

并使 getNextNumber() 推到该向量上:

numbers.pop_front();
used.emplace_back(next, std::chrono::system_clock::now());
return next;

releaseNumber()从中删除:

auto it = std::find_if(used.begin(), used.end(), 
    [=](const std::pair<int, timestamp>& p){
        return p.first == i;
    });
used.erase(it);

因此,ReleaseNextExpiredNumber() 只是检查 used.front() 的问题:如果过期,return releaseNumber(),否则 return -1。

首先,由于您使用的是连续的数字序列,因此您可以只使用数组将每个数字映射到一段数据:

dataArray[number - min] = whateverData;

其中 dataArraymax - min - 1 个元素(因为两个边界都包括在内)。

其次,您可以使用一个简单的 bool 数组来确定数字是否可用(bool[]std::vector<bool> 都可以)。

就跟踪过期而言,您需要使用 time function for getting timestamps, and an array of time_t 结构来存储每个号码的过期时间。然后,您的 ReleaseNextExpiredNumber 可能看起来像这样:

time_t curTime = time(NULL); // get current time
for (int num = min; num <= max; num++) // or whatever number type you want
{
    if (available[num - min]) // skip available numbers
        continue;
    if (expirationTimes[num - min] > curTime) // you can compare via difftime
    {
        // release the number
        ReleaseNumber(num);
        return num;
    }
}
// nothing expired
return -1;

GetNextNumber 相当简单:

time_t curTime = time();
for (int num = min; num <= max; num++)
{
    if (!available[num - min])) // skip unavailable numbers
        continue;
    // mark as used
    available[num - min] = false;
    // compute expiration time
    expirationTimes[num - min] = curTime + expirationTime; // or whatever function to add seconds to a time_t
    // return it
    return num;
}
// nothing available
return -1;

这是一个有趣的问题,主要是因为很难维护时间队列和可用数量。我决定把它们分开。

此外,如果您希望有大量可用数字,那么循环缓冲区可能会比我的实现更好。

您有 minmax 作为 public 变量。我认为你做不到。如果其中一个发生变化,您将需要更新所有内容,可能会导致丢掉所有数字。在我的实现中,我刚刚将它们设为 const.

无论如何,这是我想出的:

class MyClass{
public:
    MyClass(int min, int max, int expirationTime) : _min(min), _max(max), _expirationTime(expirationTime), _isAvailable(_max - _min + 1, true) {}

    int getExpirationTime() const { return _expirationTime; }

    void setExpirationTime(int expirationTime){ _expirationTime = expirationTime; }

    int getNextNumber(){
        auto it = find(_isAvailable.begin(), _isAvailable.end(), true);
        auto result = it == _isAvailable.end() ? -1 : distance(_isAvailable.begin(), it) + _min;

        if (result > 0){
            *it = false;
            _fifo.push_back(make_pair(time(nullptr), result));
        }
        return result;
    }

    void releaseNumber(int number){
        assert(number >= _min && number <= _max);
        _isAvailable[number - _min] = true;
        _fifo.erase(find_if(_fifo.begin(), _fifo.end(), [&](const pair<time_t, int> i){ return i.second == number; }));
    }

    int releaseNextExpiredNumber(){
        auto result = -1;

        if(!_fifo.empty() && _fifo.front().first <= time(nullptr) + _expirationTime){
            result = _fifo.front().second;
            _isAvailable[result - _min] = true;
            _fifo.pop_front();
        }
        return result;
    }
private:
    const int _min;
    const int _max;
    int _expirationTime;
    vector<bool> _isAvailable;
    deque<pair<time_t, int>> _fifo;
};