C++ 容器的无序迭代
我的意图是屏蔽容器的实现细节,这样客户端就不允许依赖隐式插入顺序。我试图通过改变迭代发生的顺序来实现这一点 我有一个容器,我想在迭代时对它进行随机排序。下面是一些伪代码C++ 容器的无序迭代,c++,C++,我的意图是屏蔽容器的实现细节,这样客户端就不允许依赖隐式插入顺序。我试图通过改变迭代发生的顺序来实现这一点 我有一个容器,我想在迭代时对它进行随机排序。下面是一些伪代码 namespace abc { template<class T> class RandomList { void insert(T t); T erase(T t); iterator begin(); iterator end(); } } namespace test { int main
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;
}
}
}
我的问题是,实现随机列表的最佳方式是什么。我天真的想法是只持有一个成员std::list并执行rand()来确定insert是前插还是后插
还有其他想法吗
我主要使用C++03,但我可以访问boost。我不确定我是否完全理解您的用例,但这是一个有趣的问题 Tony 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());
}
};
我不确定随机性的质量,但它应该足以确保客户不能对元素的顺序做出任何假设。我不确定我是否理解您问题的所有方面。我认为什么是好的解决方案在很大程度上取决于应用程序和
T
的类型。在任何情况下,我建议您更改RandomList
的界面(见下文)
首先,您在内部使用std::list
并在前面或后面随机插入的想法似乎是一种可能的解决方案
如果您的目标是广泛的应用,我认为使用std::vector
不是一个好主意。原因是std::vector
实现通常会在内部做很多额外的工作,这可能会损害容器类型的性能/内存需求。通常std::vector
使用大于向量实际大小的内存缓冲区,以避免在后续调用插入操作(如push_back
)时进行分配。因此,在后续调用中插入的性能可能会有所不同(突然,向量缓冲区必须再次增加…),并且容器可能会使用比实际需要多得多的内存。再次强调:这可能不是问题,这取决于您的应用程序和T
实际是什么。但是作为RandomList
界面的用户,我很想知道它在内部使用std::vector
因此,如果您只是要求“另一个想法”,并且还希望允许将相同值的多个插入到容器中-这里有一个:本质上,将RandomList
放入std::map
包装器中。随机化应通过正确使用map键来实现。要迭代容器,只需使用std::map
的迭代器。这实际上提供了对(键、值)对的引用。您可以使用std::map
键来实现可能感兴趣的其他特性。首先,通过引入typedefRandomList::handle
,可以隐藏密钥类型,从而隐藏实现的细节。我建议方法insert
实际返回一个RandomList::handle
。通过这种方式,您允许用户通过向界面添加方法access
直接访问特定的容器值access
将RandomList::handle
作为参数,并返回对相应映射值的引用。如果以这种方式更改接口,则您的RandomList
迭代器(即映射迭代器)引用的是RandomList::handle
和T
对,这是一致的。这是否有用很大程度上取决于T
将是什么
erase
应将RandomList::handle
作为参数。再次强调:如果不欢迎多个插入,那么基于std::map
实现的想法是有问题的,或者至少应该以不同的方式来实现
这种方法允许整洁地控制“随机化实现”。例如,如果在前面或后面使用带有随机插入的std::list
,则随机化的实现与内部存储/容器实现密切相关。基于std::map
的实现可以使随机化的细节与实现的其余部分更加分离,因为随机化在map键选择方面是完全受控的。例如,如果使用int
作为键,则可以在内部保留一个计数器,该计数器在后续插入时递增。计数器的当前值用作下一次贴图插入的键。由于这将导致一个完全不平衡的树结构,RandomList
在迭代中的行为类似于普通的std::list
。我建议选择新的键值,使树完全平衡。通过这种方式,直接访问功能的实现效率最高(因为搜索操作很快),通过begin()
/end()
直接迭代RandomList
/std::map
应该会得到足够“随机”的结果
最后,关于界面:我建议您在
insert
操作中使用完美转发,而不是直接复制。这样,在将元素插入std::map
(当然std::list
也是如此)时,可以避免不必要的复制操作,并且还允许进行移动操作。如果您不想使用完美转发,至少将T
更改为const T&
,因为将对象插入std::list
/std::map
容器需要另一个复制操作。我会避免随机插入,并随机迭代。大容器的想法是正确的,这将是错误的
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());
}
};
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);
}
};