Добавление к вектору при итерации по нему?

У меня есть вектор, которого я выполняю итерации. При итерации я могу добавить новые значения к вектору. Это смотрит что-то как:

struct Foo
{
   bool condition;
};

void AppendToVec(vector<Foo>& v)
{
   ...
   v.push_back(...);
}

vector<Foo> vec;
...
for (vector<Foo>::size_type i = 0; i < vec.size(); ++i)
{
   if (vec[i].condition) AppendToVec(vec);
}

Это хорошо работает и на самом деле изящно обрабатывает случай, где недавно добавленные элементы рекурсивно требуют, чтобы еще больше элементов было добавлено, но это чувствует себя немного хрупким. Если кто-то еще приезжает и настраивает цикл, он может легко быть поврежден. Например:

//No longer iterates over newly appended elements
vector<Foo>::size_type size = vec.size();
for (vector<Foo>::size_type i = 0; i < size; ++i)
{
   if (vec[i].condition) AppendToVec(vec);
}

или

//Vector resize may invalidate iterators
for (vector<Foo>::iterator i = vec.begin(); i != vec.end(); ++i)
{
   if (vec->condition) AppendToVec(vec);
}

Там какие-либо лучшие практики должны обработать случаи как это? Комментирует цикл с "Предупреждением: Этот цикл, намеренно добавляет к вектору при итерации. Изменить осторожно" лучший подход? Я открыт для переключающихся контейнеров также, если это делает вещи более устойчивыми.

23
задан JRM 9 August 2010 в 19:21
поделиться

7 ответов

Если кто-то другой придет и поправит цикл, его легко разорвать.

Тогда не используйте for цикл , используйте вместо него цикл while . Для меня цикл for всегда подразумевает простой итерационный цикл с использованием счетчика . Однако, если я сталкиваюсь с циклом while , мне кажется, что все было слишком сложно, чтобы выразить их в простом цикле for . Я буду смотреть внимательнее и более осторожен с "оптимизацией" циклов while , чем с for циклов.

13
ответ дан 29 November 2019 в 02:08
поделиться

Мой подход к этой проблеме часто заключается в создании очереди, в которую я добавляю все новые элементы, а затем, после итерации исходного контейнера, обрабатываю элементы в очереди и/или добавляю их к исходному.

Положительные стороны этого подхода в том, что происходящее очевидно, и он работает в сценариях, где несколько потоков могут ставить в очередь новые элементы.

20
ответ дан 29 November 2019 в 02:08
поделиться

Разрешить AppendToVec обновлять i, если vec был перераспределен, используя относительную позицию в старом векторе (i-vec.begin()).

Code:

void AppendToVec(vector<int> & vec, vector<int>::iterator & i)
{
    const int some_num = 1;
    const size_t diff = i-vec.begin();
    vec.push_back(some_num);
    i = vec.begin()+diff;
}

int main(int argc, char* argv[])
{    
     const size_t arbit_size = 10;
     const size_t prior_size = 3;
     vector<int> vec;

     // Fill it up with something
     for(size_t i = 0; i < prior_size; ++i) vec.push_back(static_cast<int>(i));

     // Iterate over appended elements
     vector<int>::iterator i = vec.begin();
     while(i != vec.end())
     {
      cout<<*i<<","<<vec.size()<<endl;
      if(vec.size()<arbit_size) AppendToVec(vec,i);
      ++i;
     }

     return 0;
}

Output:

0,3
1,4
2,5
1,6
1,7
1,8
1,9
1,10
1,10
1,10
5
ответ дан 29 November 2019 в 02:08
поделиться

Вам нужен произвольный доступ к этому вектору? Если нет, то std :: list более подходит. Я лично считаю, что добавление к вектору во время его итерации - не лучшая идея.

0
ответ дан 29 November 2019 в 02:08
поделиться

Вы можете добавить в двухстороннюю очередь, не делая недействительными итераторы ее элементов. Произвольный доступ близок по эффективности к вектору, поэтому часто его лучше заменить.

0
ответ дан 29 November 2019 в 02:08
поделиться

Не слишком отличается от оригинала. Просто уточню некоторые моменты:

for (vector<Foo>::size_type i = 0, n = vec.size(); i < n; ++i)
  if (vec[i].condition){
    AppendToVec(vec);
    n = vec.size();
  }
-1
ответ дан 29 November 2019 в 02:08
поделиться

Как уже указывалось, и как вы уловили, здесь есть проблема. Однако у вас есть несколько возможных решений, так что не заряжайте вслепую.

  • Большой жирный комментарий в верхней части цикла с тегом WARNING или любым другим, что требует ваш стандарт кодирования, чтобы предупредить будущего сопровождающего о том, что здесь есть какая-то уловка. Лучше всего не использовать, но может сработать.
  • Если вы можете заранее знать, сколько элементов будет добавлено (или у вас относительно жесткая верхняя граница), вы можете использовать резерв и вообще предотвратить перераспределение.
  • Использование std :: deque . Большинство характеристик производительности схожи, однако вы можете добавлять и добавлять новые значения без аннулирования итераторов / ссылок и т. Д. Здесь выглядит естественным образом
  • Использование отдельной очереди и двойного цикла

Я думаю, что deque - лучшее решение. Он соответствует вашему алгоритму, и вам не нужно беспокоиться о проблемах. Вероятно, вы могли бы заменить большую часть вектора в своем коде на deque . И если вы не хотите изменять интерфейс:

  • Скопируйте вектор в двухстороннюю очередь
  • Вычислить
  • Назначьте содержимое двухсторонней очереди в вектор

не будет включать намного больше копий, чем просто дважды перераспределение вектора . Так что не стесняйтесь!

void treat(std::vector<int>& vec)
{
   // WARNING: we use a deque because of its iterator invalidation properties
   std::deque<int> deq(vec.begin(), vec.end());

   for (std::deque<int>::const_iterator it = deq.begin(); it != deq.end(); ++it)
   {
     if (it->condition()) deq.push_back(func(*it));
   }

   vec.assign(deq.begin(), deq.end());
}
1
ответ дан 29 November 2019 в 02:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: