容器的无序迭代

时间:2015-07-21 03:22:39

标签: c++

我的目的是掩盖容器的实现细节,以便不允许客户端依赖隐式的插入顺序。我试图通过某种方式改变迭代发生的顺序来强制执行。

我有一个容器,我希望在迭代时随机排序。这是一些伪代码。

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。

3 个答案:

答案 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来允许用户直接访问特定容器值。 accessRandomList::handle作为参数,并返回对相应映射值的引用。如果以这种方式更改接口,则RandomList迭代器(只是映射迭代器)引用RandomList::handleT对是一致的。这是否有用在很大程度上取决于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)

我会避免随机插入,并随机化迭代。大容器正确的想法很难在视觉上看到,并且理解插入问题很难