我的目的是掩盖容器的实现细节,以便不允许客户端依赖隐式的插入顺序。我试图通过某种方式改变迭代发生的顺序来强制执行。
我有一个容器,我希望在迭代时随机排序。这是一些伪代码。
namespace abc
{
template<class T>
class RandomList
{
void insert(T t);
T erase(T t);
iterator begin();
iterator end();
}
}
namespace test
{
int main()
{
RandomList<int> list;
list.insert(1);
list.insert(2);
list.insert(3);
for (typename RandomList::iterator iter = list.begin();
iter != list.end(); ++iter)
{
std::cout << iter << std::endl;
}
}
}
输出:
3, 1, 2
我的问题是,实现RandomList的最佳方法是什么。我天真的想法是只持有一个成员std :: list并执行rand()以确定insert是插入前插入还是后插入。
还有其他想法吗?
我主要使用C ++ 03,但我可以访问boost。
答案 0 :(得分:1)
我不确定我是否完全理解您的用例,但它仍是一个有趣的问题。
托尼D建议使用std::vector
似乎是个好人。我将插入的值放在最后,然后用随机元素交换:
template<typename T>
class RandomList {
std::vector<T> list;
RandomIndex randomIndex;
public:
using iterator = typename std::vector<T>::const_iterator;
iterator begin() { return list.begin(); }
iterator end() { return list.end(); }
void insert(const T& t) {
list.push_back(t);
auto i = randomIndex(list.size());
using std::swap;
swap(list[i], list.back());
}
};
其中RandomIndex
是一个(非模板化的)帮助函子来获取随机索引:
class RandomIndex {
std::mt19937 eng;
public:
RandomIndex() : eng(std::random_device{}()) {}
size_t operator()(size_t size) {
auto dist = std::uniform_int_distribution<size_t>{0, size - 1};
return dist(eng);
}
};
我不确定随机性的质量,但它应该足以确保客户不能对元素的顺序做出任何假设。
答案 1 :(得分:0)
我不确定我是否了解您问题的所有方面。我认为什么是合格的解决方案在很大程度上取决于应用程序以及T
在实践中的类型。无论如何,我建议你改变RandomList
的界面(见下文)。
首先,您在内部使用std::list
并在前面或后面随机插入的想法似乎是一种可能的解决方案。
如果您的目标是广泛的应用程序,我认为使用std::vector
不是一个好主意。原因是std::vector
实现通常会在内部执行大量额外操作,这可能会损害容器类型的性能/内存要求。通常std::vector
使用大于向量实际大小的内存缓冲区,以避免在后续调用push_back
之类的插入操作时进行分配。因此,插入的性能可能会随后调用而变化(突然必须再次增加向量缓冲区...),并且您的容器可能会使用比实际需要更多的内存。再说一遍:这可能不是问题,这取决于您的应用程序以及T
实际上是什么。但作为RandomList
界面的用户,我会很难听到它在内部使用std::vector
...
所以如果你只是要求&#34;另一个想法&#34;并且还希望允许在容器中多次插入相同的值 - 这里是一个:实质上,将RandomList
变成std::map
包装器。应该通过适当地使用映射密钥来实现随机化。要迭代容器,只需使用std::map
的迭代器。这实际上为您提供了对(键,值)对的引用。您可以使用std::map
键来实现可能有趣的其他功能。首先,您可以通过引入typedef RandomList::handle
来隐藏密钥类型 - 从而隐藏实现的详细信息。我建议方法insert
实际返回RandomList::handle
。通过这种方式,您可以通过向接口添加方法access
来允许用户直接访问特定容器值。 access
将RandomList::handle
作为参数,并返回对相应映射值的引用。如果以这种方式更改接口,则RandomList
迭代器(只是映射迭代器)引用RandomList::handle
和T
对是一致的。这是否有用在很大程度上取决于T
将会是什么。
erase
应以RandomList::handle
为参数。再说一次:如果不欢迎多次插入,那么将实施基于std::map
的想法是有问题的,或者至少应以不同的方式进行处理。
这种方法可以巧妙地控制随机化实施&#34;。例如,如果在前面或后面使用std::list
随机插入,则随机化的实现与内部存储/容器实现密切相关。基于std::map
的实现可以使随机化的细节与实现的其余部分更加分离,因为随机化在映射密钥选择方面完全受控。例如,如果使用int
作为键,则可以在内部保留一个计数器,该计数器在后续插入时递增。计数器的当前值用作下一个地图插入的键。由于这会导致完全不平衡的树结构,RandomList
在迭代时表现得像普通的std::list
。我建议选择新的键值,使树完全平衡。通过这种方式,可以最有效地实现直接访问功能(导致搜索操作很快),并且RandomList
/ std::map
通过begin()
/ end()
的直接迭代应该是导致充分&#34;随机&#34;结果
最后,关于界面:我建议你使用insert
操作的完美转发而不是直接复制。通过这种方式,您可以在将元素插入std::map
时避免不必要的复制操作(当然,std::list
也是如此),并且您还允许移动操作。如果您不想使用完美转发,请至少将T
更改为const T&
,因为需要进行另一次复制操作才能将对象插入std::list
/ std::map
容器。
答案 2 :(得分:0)
我会避免随机插入,并随机化迭代。大容器正确的想法很难在视觉上看到,并且理解插入问题很难