Подтвердить что ты не робот

Самый эффективный способ стирания/удаления нескольких элементов std::vector при сохранении исходного порядка?


У меня есть std::vector<int> и второй контейнер, содержащий итераторы или индексы (без ключей, я хочу постоянного доступа к элементу) к этому вектору для целей удаления. Предположим, что у меня есть вектор из 1000 элементов и вы хотите стереть 200 из них. Порядок удаленных элементов должен быть таким же после операций удаления, как и раньше.

Еще одна вещь, которую я пропустил в первой версии моего вопроса: значения уникальны. Они являются тождествами.

Как вы это сделаете в безопасном (в отношении правил stl) и эффективным образом (решение для вектора должно быть окончательным)?

Возможности или Методы, о которых я думал:

  • erase-remove idiom (http://en.wikipedia.org/wiki/Erase-remove_idiom): первоначально для удаления элементов, которые выполняют условие (включая линейный поиск), но я подумайте с диапазонами размеров 1, этот метод можно было бы использовать с уже предоставленными итераторами и фиктивным состоянием. Вопрос: является ли первоначальный порядок сохраненных элементов и является ли он более эффективным, чем последний метод?
  • перебирать индексы и стирать элементы с помощью vector.erase(vector.begin()+index+offset), сохраняя при этом индексы в контейнере для вычисления смещения. Это смещение может быть определено для каждой итерации удаления с использованием std::lower_bound n контейнера уже удаленных элементов. Проблема: много бинарных_значений для получения смещения и большого количества операций перемещения из-за удаления случайного местоположения.
  • В настоящий момент я делаю следующее: получаю все итераторы для удаляемых элементов. Сортируйте их в порядке убывания в соответствии с местоположением в векторе и обведите их над окончательным удалением с помощью vector.erase. Теперь я не признал недействительным какой-либо итератор, и нет никаких операций перегруппировки векторов, кроме самого удаления. Проблема: много сортировки

Итак, как бы вы справились с этим? Любые новые идеи? Любые рекомендации?

Спасибо за ваш вклад.

Саша

Изменить/обновить/Собственные результаты: Я реализовал erase-remove idiom, о котором также упоминал KennyTM, с предикатом , основанный на поиске в boost:: dynamic_bitset и безумно быстро. Кроме того, я попробовал метод PigBen move-and-truncate (также упоминаемый Стивом Джессопом), который также обращается к битете в нем while-loop. Оба кажутся одинаково быстрыми с моими данными. Я попытался удалить 100 из 1000 элементов (unsigned ints), сделал это 100 удалений 1M раз, и не было существенной разницы. Поскольку я думаю, что stl-based erase-remove idiom более естественна, я выбираю этот метод (аргумент также упоминался KennyTM).

4b9b3361

Ответ 1

В <algorithm> есть функция remove_if, которая сжимает все значения, которые не удаляются в начале, поддерживая порядок. Это работает, если эти 200 элементов могут быть чисто определены значениями, а не индексом.

Это, по сути, идиома Erase-remove, с которой вы связались. remove_if гарантируется выполнение сравнений O (N) (и не более O (N)), что было бы более эффективно, чем сортировка (O (N log N)), хотя ваш последний вариант фактически не требует сортировки, если индексы определяются по значениям (просто сканируйте в обратном направлении при копировании).

Тем не менее использование remove_if (если возможно) лучше, чем другие 2 варианта, потому что реализация уже написана для вас, так что меньше шансов на логическую ошибку и лучше передает то, что (а не как) делать.

Ответ 2

Как прокручивать вектор, и для каждого элемента, который нужно удалить, скопируйте следующий элемент, который не нужно удалять в эту позицию. Затем, когда вы дойдете до конца, обрезайте его.

int last = 0;
for(int i=0; i<vec.size(); ++i, ++last)
{
   while(needs_to_be_removed(i))
      ++i;
   if(i >= vec.size()) break;

   vec[last] = vec[i];   
}

vec.resize(last);

Ответ 3

Прежде всего, не назовите erase больше времени, чем вам нужно, потому что для вектора он перемещает все последующие элементы вниз, давая всю операцию a & Omega; (n * m) наихудшее время выполнения ( n размер вектора, m - размер списка индексов для удаления).

Я думаю, что первое, что я попробую, будет похоже на ваш текущий код:

  • сортировать индексы
  • создать новый вектор размера n - m
  • перебирать исходный вектор, копировать элементы indexes[0], пропустить элемент, затем скопировать элементы indexes[1] - indexes[0] - 1, пропустить элемент и т.д.
  • swap исходный вектор с новым.

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

Вы могли бы избежать второго шага, используя back_inserter вместо того, чтобы назначать вектор, хотя вы, вероятно, по-прежнему сохраняете пространство заранее.

[Edit: подумайте, почему я что-то копирую? Вместо реализации измененного remove_copy_if, выполните модифицированный remove_if и просто скопируйте в более раннюю точку вектора. Затем erase/resize в конце. Я бы не стал беспокоиться о типе индексов O(m log m), пока не будет доказано, что это проблема, потому что вряд ли она будет значительно медленнее, чем операция & Omega; (m), чтобы читать все значения, которые нужно удалить, и хранить их в некоторых вид контейнера. Тогда использование этого контейнера в предикате до remove_if может быть или не быть O(1). Сортировка может оказаться быстрее для правдоподобных значений m.]

Ответ 4

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

Сделайте свой второй контейнер картой, чтобы он автоматически сортировал для вас индексы.

изменить:

Чтобы ответить на комментарий

Стоимость сохранения карты в худшем случае такая же, как и сохранение другой структуры (списка или вектора), а затем ее сортировки. Если вы уже это делаете, вы можете сохранить его как карту. Не имеет смысла жаловаться на накладные расходы карты и накладные расходы на сортировку списка.

Что касается производительности моего предложенного алгоритма, если m - количество элементов, подлежащих удалению, а n - общее количество элементов, то это приводит к O (n - m).

Конечно, это в основном просто смущает вашу попытку оптимизации с помощью вектора.

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

2 - Вместо того, чтобы поддерживать вторую структуру данных, отметьте элементы, которые необходимо удалить непосредственно в их контейнере. Тривиальный способ заключается в использовании контейнера <T> используйте контейнер < станд:: Пара < T, char → и используйте char для отслеживания состояния элемента.

Если вы делаете 1 и 2, вы полностью удаляете все копии и получаете гораздо более эффективную реализацию.

Ответ 5

Элементы чего? Возможно, я серьезно отношусь к вашему сообщению, но если у вас есть вектор из 1000 элементов, почему бы не отметить те, которые больше не действительны, и покончить с стиранием в первую очередь. Очевидно, я делаю предположение, что ваши элементы не требуют большой памяти.

Я только объясню это, потому что вы, похоже, относитесь к скорости. Если предложенные предложения не делают трюк, возможно, эта идея стоит подумать! По сути, ускоряйте работу, не делая операцию в первую очередь.

Ответ 6

Если у вас есть (например, неупорядоченный) набор индексов, которые вы хотите удалить, вы можете использовать это:

template <typename Type>
void erase_indices(
        const std::unordered_set<size_t>& indices_to_erase,
        std::vector<Type>& vec) {
    std::vector<bool> erase_index(vec.size(), false);
    for (const size_t i: indices_to_erase) {
        erase_index[i] = true;
    }
    std::vector<bool>::const_iterator it_to_erase = erase_index.cbegin();
    typename std::vector<Type>::iterator it_erase_from = std::remove_if(
        vec.begin(), vec.end(),
        [&it_to_erase](const Type&) -> bool {
          return *it_to_erase++ == true;
        }
    );
    vec.erase(it_erase_from, vec.end());
}

Это самое быстрое решение, которое пришло мне в голову. Вам нужно С++ 11. Пример использования для стирания элементов с индексами 2 и 5:

constexpr size_t num = 10u;
std::vector<int> vec(num);
std::iota(vec.begin(), vec.end(), 0);

std::unordered_set<size_t> indices_to_erase;
indices_to_erase.insert(2u);
indices_to_erase.insert(5u);

erase_indices(indices_to_erase, vec);

До:

0 1 2 3 4 5 6 7 8 9

После:

0 1 3 4 6 7 8 9

Edit: Если вы хотите быть более гибкими в отношении типа контейнера, в котором содержатся индексы для стирания:

template <typename Type, typename Container>
void erase_indices(
        const Container& indices_to_erase,
        std::vector<Type>& vec) {
    typedef typename Container::value_type IndexType;
    static_assert(std::is_same<IndexType, std::size_t>::value,
        "Indices to be erased have to be of type std::size_t");
    std::vector<bool> erase_index(vec.size(), false);
    for (const IndexType idx_erase: indices_to_erase) {
        erase_index[idx_erase] = true;
    }
    std::vector<bool>::const_iterator it_to_erase = erase_index.cbegin();
    typename std::vector<Type>::iterator it_erase_from = std::remove_if(
        vec.begin(), vec.end(),
        [&it_to_erase](const Type&) -> bool {
          return *it_to_erase++ == true;
        }
    );
    vec.erase(it_erase_from, vec.end());
}

Теперь вы можете использовать любой вид контейнера из Библиотека контейнеров, чтобы предоставить индексы, подлежащие стиранию, пока value_type этот контейнер std::size_t. Использование остается тем же.

Ответ 7

Я написал функцию на основе ответа Бенджамина Линдли fooobar.com/info/319397/....

#include <iostream>
#include <algorithm>
#include <vector>

template <typename elementType, typename indexType>
void remove_multiple_elements_from_vector(std::vector<elementType> &vector,
std::vector<indexType> &indexes)
{
    // 1. indexType is any integer.
    // 2. elementType is any type.
    // 3. Indexes should be unique.
    // 4. The largest index inside indexes shouldn't be larger than
    //    the largetst index in the vector.
    // 5. Indexes should be sorted in ascending order
    //    (it is done inside function).
    std::sort(indexes.begin(), indexes.end());
    indexType currentIndexInIndexesVector = 0;
    indexType last = 0;
    for(indexType i=0; i<vector.size(); ++i, ++last)
    {
       while(indexes[currentIndexInIndexesVector] == i)
       {
          ++i;
          ++currentIndexInIndexesVector;
       }
       if(i >= vector.size()) break;

       vector[last] = vector[i];   
    }

    vector.resize(last);
}


int main()
{
    std::vector<int> vector = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> indexes = {0, 10, 5};

    for (auto &vectorElement : vector)
    {
        std::cout << vectorElement << " ";
    }    
    std::cout << "\n";

    remove_multiple_elements_from_vector<int, int>(vector, indexes);

    for (auto &vectorElement : vector)
    {
        std::cout << vectorElement << " ";
    }
}