我需要独特的可重复使用的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
答案 0 :(得分:7)
我的想法是使用std::set
和Boost.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)
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。/ p>
答案 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快速用于我的用例。