我正在编写一个Composite类(类似于Qt的QObject
),目前我将这些子项存储在std::vector
中。
每个Composite实例都有一个名称,并且该名称在所有其他实例之间必须是唯一的,这些实例是这个实例的兄弟,或者可能更好地说是在共享同一父实例的实例之间。
每次在vector
内推送新实例时,我必须查看其名称是否已被vector
中已有的实例使用过,如果有,我必须更改其名称添加一个数字。
当孩子的数量变得一致时,我提出的代码非常愚蠢且非常慢。
这是班级:
class Composite
{
public:
Composite(const std::string &name, Composite *ancestor=NULL);
~Composite();
private:
std::string name;
Composite *ancestor;
std::vector<Composite*> descendants;
public:
void setName(const std::string &name);
};
这是构造函数和setName
实现:
Composite::Composite(const std::string &name, Composite *ancestor)
{
this->ancestor = ancestor;
setName(name);
if (ancestor!=NULL)
ancestor->descendants.push_back(this);
}
void Composite::setName(const std::string &name)
{
this->name = name;
if (ancestor!=NULL)
{
CompositeList::iterator dIt;
for( dIt=ancestor->descendants.begin(); dIt!=ancestor->descendants.end(); dIt++)
{
if ((*dIt)==this)
{
continue;
}
else if (this->name == (*dIt)->getName())
{
int trailingNumber = stringToInt(getTrailingNumber(this->name));
std::string cleanName = removeTrailingNumber(this->name);
this->name = cleanName+intToString(trailingNumber+1);
}
}
}
}
对于很少的孩子来说这可能没问题,但是当他们变成数百人时,setName
功能真的变慢了。
想象一下这种情况:
Composite *parent = new Composite("pippo");
for (int i=0; i<10000; i++)
{
Composite("ClashingName", parent);
}
第一次没问题,第二次在ClashingName0中更改名称,第三次将名称首次变为ClashingName0,而不是发现与第二个实例的冲突并将名称设置为ClashingName1 ...你得到了这个想法,它是指数级的,当它在该循环结束时,通过了不可接受的时间量。
这里真正的问题是如何有效地找到名称冲突并有效地分配一个尚未使用的新名称? 任何std容器都适合我,我的编译器支持C ++ 11,但我不能/想要使用Boost,因为我正在处理的项目非常小(基本上是这个类)
我不是C ++的经验丰富的用户,我想使用map
或unordered_map
,但我真的很想接受专家的建议。
答案 0 :(得分:2)
IMO,您需要更改对象的存储方式。如下所示:
std::map<std::string, std::vector<Composite> >
映射的关键是前缀,向量中的索引是第n个Composite
对象。您需要提供一个自定义查找功能,用于拆分传入的名称...例如
std::string query = "pipo_10";
在查找功能中,
Composite* lookup(std::string const& key)
{
// split the key to get the prefix and the index
// lookup in the map using the prefix
// return the index
}
编辑1:要保存所有字符串操作,您可以定义自己的自定义键,它只是一对(例如,std::pair<std::string, int>
,它是前缀和索引),并且您的查找只会使用此键中的值。
<击> 编辑2:更多地考虑这个,更好地有一张地图,例如
std::map<std::string, std::map<int, Composite> >
现在,而不是索引是向量中的索引,它是第二个映射中的查找。这样可以更好地处理删除,关键是我之前说的复合键(对)。
击>
归功于@Steves的建议..
std::map<std::pair<std::string, int>, Composite>
使用lower_bound()
技巧找到给定Composite
答案 1 :(得分:1)
map
或unordered_map
将执行此任务,使用count
函数来测试名称是否在地图中,以及find
函数或{ {1}}访问它。
operator[]
整体上可能会快一点。
unordered_map
可以更好地处理您讨厌的map
示例,因为您可以使用"ClashingName"
或lower_bound
在单个查找中查找最后一个冲突名称,而不是查找1}} upper_bound
依次提升每个人。
请注意,ClashingName0
默认按字典顺序排序,因此ClashingName9999
位于map
之前。如果某人为您提供了包含数字的名称,尤其是最后的名称,那么也会出现问题。
使用Nim的建议修复此问题 - 使用一对ClashingName10
作为地图密钥,并根据需要从该对构造名称。同样,当有人为您提供以数字结尾的名称时,您必须做一些特别的事情。确保名称ClashingName9
不能出现两次,一次为string, int
,另一次为"Clash10"
。一个简单的选择是禁止提供名称中的一个字符,并将其用作分隔符。
答案 2 :(得分:0)
如果您不介意每个对象有一个额外的地图,您可以执行以下操作:
// inside Composite definiton
std::map<std::string, int> names_of_descendants;
然后简单地说:
void Composite::setName(const std::string &name)
{
if (ancestor)
{
// for the first usage of certain name, map's operator[]
// will insert default constructed int (0) in the map
this->name = name + ancestor->names_of_descendants[name];
// increment the value for this name, for the next call of setName
++names_of_descendants[name];
}
}
您可以保留用于存储后代的向量。