用于C ++中唯一可重用的id的最快容器或算法

时间:2010-04-12 06:25:42

标签: c++

我需要独特的可重复使用的ID。用户可以选择自己的ID,也可以要求免费。 API基本上是

class IdManager {
public:
  int AllocateId();          // Allocates an id
  void FreeId(int id);       // Frees an id so it can be used again
  bool MarkAsUsed(int id);   // Let's the user register an id. 
                             // returns false if the id was already used.
  bool IsUsed(int id);       // Returns true if id is used.
};

假设id恰好从1开始,进展,2,3等等。这不是一个要求,只是为了帮助说明。

IdManager mgr;
mgr.MarkAsUsed(3);
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());
printf ("%d\n", mgr.AllocateId());

会打印

1
2
4

因为id 3已经被声明使用了。

最好的容器/算法是什么才能记住使用哪些ID并找到一个免费ID?

如果你想知道一个特定的用例,OpenGL的glGenTextures,glBindTexture和glDeleteTextures等同于AllocateId,MarkAsUsed和FreeId

8 个答案:

答案 0 :(得分:7)

我的想法是使用std::setBoost.interval,因此IdManager将保留一组非重叠的免费ID区间。 AllocateId()非常简单且非常快速,只返回第一个空闲间隔的左边界。其他两种方法稍微困难一些,因为可能需要拆分现有间隔或合并两个相邻间隔。但它们也很快。

所以这是使用区间的想法的例证:

IdManager mgr;    // Now there is one interval of free IDs:  [1..MAX_INT]
mgr.MarkAsUsed(3);// Now there are two interval of free IDs: [1..2], [4..MAX_INT]
mgr.AllocateId(); // two intervals:                          [2..2], [4..MAX_INT]
mgr.AllocateId(); // Now there is one interval:              [4..MAX_INT]
mgr.AllocateId(); // Now there is one interval:              [5..MAX_INT]

这是代码本身:

#include <boost/numeric/interval.hpp>
#include <limits>
#include <set>
#include <iostream>


class id_interval 
{
public:
    id_interval(int ll, int uu) : value_(ll,uu)  {}
    bool operator < (const id_interval& ) const;
    int left() const { return value_.lower(); }
    int right() const {  return value_.upper(); }
private:
    boost::numeric::interval<int> value_;
};

class IdManager {
public:
    IdManager();
    int AllocateId();          // Allocates an id
    void FreeId(int id);       // Frees an id so it can be used again
    bool MarkAsUsed(int id);   // Let's the user register an id. 
private: 
    typedef std::set<id_interval> id_intervals_t;
    id_intervals_t free_;
};

IdManager::IdManager()
{
    free_.insert(id_interval(1, std::numeric_limits<int>::max()));
}

int IdManager::AllocateId()
{
    id_interval first = *(free_.begin());
    int free_id = first.left();
    free_.erase(free_.begin());
    if (first.left() + 1 <= first.right()) {
        free_.insert(id_interval(first.left() + 1 , first.right()));
    }
    return free_id;
}

bool IdManager::MarkAsUsed(int id)
{
    id_intervals_t::iterator it = free_.find(id_interval(id,id));
    if (it == free_.end()) {
        return false;
    } else {
        id_interval free_interval = *(it);
        free_.erase (it);
        if (free_interval.left() < id) {
            free_.insert(id_interval(free_interval.left(), id-1));
        }
        if (id +1 <= free_interval.right() ) {
            free_.insert(id_interval(id+1, free_interval.right()));
        }
        return true;
    }
}

void IdManager::FreeId(int id)
{
    id_intervals_t::iterator it = free_.find(id_interval(id,id));
    if (it != free_.end()  && it->left() <= id && it->right() > id) {
        return ;
    }
    it = free_.upper_bound(id_interval(id,id));
    if (it == free_.end()) {
        return ;
    } else {
        id_interval free_interval = *(it);

        if (id + 1 != free_interval.left()) {
            free_.insert(id_interval(id, id));
        } else {
            if (it != free_.begin()) {
                id_intervals_t::iterator it_2 = it;
                --it_2;
                if (it_2->right() + 1 == id ) {
                    id_interval free_interval_2 = *(it_2);
                    free_.erase(it);
                    free_.erase(it_2);
                    free_.insert(
                        id_interval(free_interval_2.left(), 
                                    free_interval.right()));
                } else {
                    free_.erase(it);
                    free_.insert(id_interval(id, free_interval.right()));
                }
            } else {
                    free_.erase(it);
                    free_.insert(id_interval(id, free_interval.right()));
            }
        }
    }
}

bool id_interval::operator < (const id_interval& s) const
{
    return 
      (value_.lower() < s.value_.lower()) && 
      (value_.upper() < s.value_.lower());
}


int main()
{
    IdManager mgr;

    mgr.MarkAsUsed(3);
    printf ("%d\n", mgr.AllocateId());
    printf ("%d\n", mgr.AllocateId());
    printf ("%d\n", mgr.AllocateId());

    return 0;
}

答案 1 :(得分:1)

通常情况下,我会坚持一个简单的实现,直到你知道哪些方法最常用。过早的调整可能是错误的。使用简单的实现,并记录其使用,然后您可以从最常用的功能进行优化。如果您只需要几百个ID并且只需一个简单的向量即可用于优化快速删除或快速分配。

答案 2 :(得分:0)

但我不认为你必须保证id必须从1开始。你可以确保可用的id必须大于所有已分配的id。

如果首先注册3,那么下一个可用的id就可以是4.我不认为有必要使用1.

答案 3 :(得分:0)

知道你应该跟踪多少个ID会很好。如果只有大约一百个,那么一个简单的set会做,用线性遍历来获得一个新的id。如果它更像是几千,那么当然线性遍历将成为性能杀手,特别是考虑到缓存不友好的集合。

就个人而言,我会采取以下措施:

  • set,有助于轻松跟踪ID O(log N)
  • 建议将新ID作为当前最大值+ 1 ... O(1)

如果你没有分配(在应用程序的生命周期内)超过max<int>() id,它应该没问题,否则......使用更大的类型(使其无符号,使用{{1}或者long)这是最容易开始的。

如果还不够,请给我留言,我会编辑并搜索更复杂的解决方案。但是记账越复杂,在实践中执行的时间越长,犯错的可能性就越大。

答案 4 :(得分:0)

我假设您希望能够使用Id类型的所有可用值,并且您希望重用已释放的ID?我还假设如果您从多个线程中使用它,您将锁定该集合......

我创建了一个类,其中包含一个用于存储已分配ID的集合,一个用于存储空闲ID的列表和一个最大分配值,以防止我必须使用每个可用ID预加载空闲ID列表。

所以你开始使用一组空的已分配ID和空闲的自由ID列表,并将最大值分配为0.你分配,如果有的话,取自由列表的头部,否则取最大值,检查它不在你有分配的id,如果有人保留了它,如果是,则增加max并再试一次,如果没有将它添加到集合中并返回它。

当你释放身份证时,你只需在你的套装中检查它,如果是这样就把它推到你的免费清单上。

要预订身份证,您只需检查该设置,如果不存在,请添加它。

这会快速回收id,这对你来说可能有用,也可能没有用,也就是说,如果没有其他线程正在访问集合,则allocate(),free(),allocate()会给你相同的id。

答案 5 :(得分:0)

压缩矢量。但我认为任何容器都不会产生明显的差异。

答案 6 :(得分:0)

skwllsp类似,我会跟踪尚未分配的范围,但我的方法略有不同。基本容器是一个映射,键是范围的上限,值是下限。

IdManager::IdManager()
{
    m_map.insert(std::make_pair(std::numeric_limits<int>::max(), 1);
}

int IdManager::AllocateId()
{
    assert(!m_map.empty());
    MyMap::iterator p = m_map.begin();
    int id = p->second;
    ++p->second;
    if (p->second > p->first)
        m_map.erase(p);
    return id;
}

void IdManager::FreeId(int id)
{
    // I'll fill this in later
}

bool IdManager::MarkAsUsed(int id)
{
    MyMap::iterator p = m_map.lower_bound(id);

    // return false if the ID is already allocated
    if (p == m_map.end() || id < p->second || id > p->first)))
        return false;

    // first thunderstorm of the season, I'll leave this for now before the power glitches
}

bool IdManager::IsUsed(int id)
{
    MyMap::iterator p = m_map.lower_bound(id);
    return (p != m_map.end() && id >= p->second && id <= p->first);
}

答案 7 :(得分:0)

所以一位朋友指出,在这种情况下,哈希可能会更好。大多数OpenGL程序不会使用超过几千个ID,因此几乎可以保证每个插槽只有1或2个条目。有一些退化的情况,许多ID可能会进入1个插槽,但这种情况不太可能发生。使用散列会使AllocateID慢得多,但可以使用一个集合。分配速度慢于InUse快速用于我的用例。