容器的无序迭代
Unordered Iteration of a Container
我的意图是掩盖容器的实现细节,这样客户端就不允许依赖隐式插入顺序。我试图通过某种方式改变迭代发生的顺序来强制执行这一点。
我有一个容器,我想在迭代时随机排序。下面是一些伪代码。
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
我的问题是,什么是最好的方法来实现随机列表。我天真的想法是只持有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());
}
};
其中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);
}
};
我不确定随机性的质量,但它应该足以确保客户端不能对元素的顺序做出任何假设。
我不确定我是否理解了你问题的各个方面。我认为什么是好的解决方案很大程度上取决于应用和T
在实践中的类型。无论如何,我建议您更改RandomList
的接口(见下文)。
首先,您在内部使用std::list
并在前面或后面随机插入的想法似乎是一个可能的解决方案。
我不认为这是一个好主意,如果你的目标是广泛的应用程序使用std::vector
。原因是std::vector
实现通常在内部做很多额外的事情,这可能会损害容器类型的性能/内存需求。通常std::vector
使用比vector的实际大小更大的内存缓冲区,以避免后续调用插入操作(如push_back
)时分配内存。因此,插入的性能在随后的调用中可能会发生变化(突然又要增加向量缓冲区……),并且容器可能会使用比实际需要更多的内存。同样,这可能不是问题,这取决于您的应用程序和T
实际是什么。但是作为RandomList
接口的用户,我会听到它在内部使用std::vector
而感到烦恼…
所以,如果你只是要求"另一个想法",也希望允许多次插入相同的值到容器中-这里有一个:本质上,使RandomList
成为std::map
包装器。随机化应该通过适当地使用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
实现的想法是有问题的,或者至少应该以不同的方式进行处理。
这种方法允许对"随机化实现"进行简洁的控制。例如,如果您使用std::list
在前面或后面随机插入,则随机化的实现与您的内部存储/容器实现密切相关。基于std::map
的实现可以将随机化的细节与其他实现分离开来,因为随机化完全由地图键选择控制。例如,如果您使用int
作为键,则可以在内部保留一个计数器,该计数器在后续插入时递增。计数器的当前值用作下一次映射插入的键。由于这将导致完全不平衡的树结构,因此RandomList
在迭代时的行为与普通std::list
一样。我建议以这样一种方式选择新的键值,即树是完美平衡的。通过这种方式,直接访问特性是最有效的实现(因为搜索操作很快),并且通过begin()
/end()
直接迭代RandomList
/std::map
应该会产生足够的"随机"结果。
最后,关于接口:我建议您对insert
操作使用完美转发,而不是直接复制。通过这种方式,在向std::map
插入元素时可以避免不必要的复制操作(当然std::list
也是如此),并且还允许移动操作。如果您不想使用完全转发,至少将T
更改为const T&
,因为将对象插入std::list
/std::map
容器需要另一个复制操作。
我会避免随机插入,随机迭代。大容器的想法是正确的,将很难从视觉上看到,并且很难理解插入问题
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++中带有List类的迭代器Segfault
- 迭代时从向量和内存中删除对象
- 如何在c++迭代器类型中包装std::chrono
- 带过滤器的现代迭代c++集合
- 怪异的迭代器行为+带有无序集的segfault
- STL 无序容器迭代器
- std::插入无序集合(或映射)的迭代器
- 如何迭代unordered_set内的无序对
- 列表迭代器与无序映射不兼容
- hashtable,无序映射迭代器用法
- 如何在C++中迭代一个无序的集合
- 将无序映射迭代器更改
- C++11 无序 map<int, Mytype* 的有序迭代器>
- 无序(或散列)映射中的迭代器
- 如何从无序映射中删除多个项目,同时迭代它
- 容器的无序迭代
- 将指针值类型为的无序映射上的迭代器转换为常量引用值类型的同一映射上的迭代器
- 无法迭代boost无序哈希映射