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
键来实现可能感兴趣的其他特性。首先,通过引入typedef
RandomList::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);    
  }
};