C ++向量元素擦除与新向量创建

时间:2017-06-03 12:47:01

标签: c++ performance vector processing-efficiency cpu-speed

我正在使用需要随机选择并有效删除的元素向量,直到满足条件,或者直到选择了所有元素。但是,在代码执行的某个后期阶段,它们实际上不会被删除,所以我需要维护一个有效的可用元素列表。我可以从第二个向量中删除元素,或者每次都可以重新创建它。请参阅下面的代码的最小版本,其中显示了每次在while循环中创建向量的示例:

    Random mRandom; // Pseudo-random number generator
    std::vector< Element* > mElements;
    for( unsigned index = 0; index < ARBITRARY_VALUE; index++ )
      mElements.push_back( new Element( ) );

    std::vector< bool > removedElements;
    bool condition = true;

    while( condition == true ) {
      std::vector< unsigned > availableIndices;

      for( unsigned index = 0; index < mElements.size( ); index++ ) {
        if( removedElements[ index ] == false )
          availableIndices.push_back( index );
      }

      if( availableIndices.size( ) > 0 ) {
        unsigned maximum = availableIndices.size( ) - 1;
        unsigned randomIndex = mRandom.GetUniformInt( maximum ); // Zero to max
        removedElements[ availableIndices[ randomIndex ] ] = true;
        Element* element = mElements[ availableIndices[ randomIndex ] ];
        condition = element->DoStuff( ); // May change condition and exit while
      } else
        break;
    }

很明显,在向量中间擦除元素需要底层系统迭代剩余的元素并移动&#39;他们到了新的有效位置。显然,如果擦除的元素接近向量的末尾,则意味着更少的迭代。

我已经阅读了一些关于删除矢量元素相关费用的帖子,但我还没有看到任何直接解决我问题的内容。移动&#39;过程是否过程?擦除后的元素引入了开销,通过创建指向有效元素的新向量,每次迭代所有元素会降低成本?就像我上面的代码示例一样。

干杯,菲尔

2 个答案:

答案 0 :(得分:1)

我无法评论解决问题的最佳方法,因为我还不确定函数或算法的实际要求是什么(即元素是否应保留顺序?不可用元素是否会再次可用?如果有,会订购物品吗?等等)

但是,关于最后一个问题:

  

在擦除之后“移动”元素的过程是否会引入开销,通过创建指向有效元素的新向量,每次迭代所有元素会更便宜?

这完全取决于移动元素的内容。如果它是一个指针,如上所述,那么你甚至可以在接近分配新向量中的内存的成本之前移动很多元素。而且“很多”我在想数百甚至数千。

在上面的代码中,看起来可用性向量是多余的。如果Element指针位于availableIndicies的向量中,则它可用。

如果我理解正确的意图,我想我可能会按以下方式重构:

#include <vector>
#include <random>

struct Element
{
  bool doStuff();
};


struct ElementAvailability
{
  ElementAvailability(std::vector<Element*> const& storage)
  : storage_(storage)
  {}

  void resync()
  {
    // will requre an allocation at most once if storage_ does not grow
    available_ = storage_;
  }

  std::size_t availableCount() const {
    return available_.size();
  }

  Element* removeAvailable(std::size_t index) {
    auto pe = available_[index];
    available_.erase(std::begin(available_) + index);
    return pe;
  }

  void makeUnavailable(std::size_t available_i)
  {
    available_.erase(std::next(std::begin(available_), available_i));
  }

private:
  std::vector<Element*> const& storage_;
  std::vector<Element*> available_;
};

// I have used a std random engine because I don't know your library
auto eng = std::default_random_engine(std::random_device()());

void test(std::vector<Element*>const& elems)
{
  auto available = ElementAvailability(elems);

  bool condition = true;
  auto getCount =[&condition, &available] () -> std::size_t 
  {
    if (condition) {
      available.resync();
      auto count = available.availableCount();
      return count;
    }
    else {
      return 0;
    }
  };

  while (auto count = getCount()) {
    auto range = std::uniform_int_distribution<std::size_t>(0, count - 1);
    auto index = range(eng);
    auto candidate = available.removeAvailable(index);
    condition = candidate->doStuff();
  }
}

答案 1 :(得分:0)

我提出的随机消除元素的问题似乎只能在O(n^2)时间和O(n)空间复杂度中解决。因为您必须传递所有元素一次并且在每次传递时必须在一系列仍然存在的元素中找到随机索引并维护此序列。可能很少有使用不同算法原语的方法。下面我介绍我的解决方案,在以CPU /内存操作友好的方式执行此操作时归档此目标。

void runRandomTasks() {
  Random mRandom; // Pseudo-random number generator
  std::vector<Element*> mElements;
  for (unsigned index = 0; index < ARBITRARY_VALUE; ++index) {
    mElements.push_back(new Element);
  }
  size_t current_size = mElements.size();
  if (!current_size)
    return;
  std::vector<Element*> current_elements(current_size, nullptr);
  for (unsigned index = 0; index < current_size; ++index) {
    current_elements[index] = mElements[index];
  }
  Element** last_ptr = &current_elements[0] + current_size - 1;

  bool condition = true;

  while (condition && current_size) {
    unsigned random_size = mRandom.GetUniformInt(current_size - 1) + 1; // Zero to max
    Element** ptr = last_ptr;
    while (true) {
      random_size -= (bool)(*ptr);
      if (random_size) {
        --ptr;
      } else {
        break;
      }
    }

    condition = (*ptr)->DoStuff(); // May change condition and exit while
    *ptr = nullptr;
    --current_size;
  }
}

关于向量元素擦除的问题,你可以在我的解决方案中找到一个寻找随机索引的循环,这相当于时间复杂度中向量中的元素擦除,但由于元素移位是一个较小的常量省略,而是将元素值检查为0。在循环中进行任何内存分配总是很昂贵,所以要避免这种情况。