Как использовать правильный указатель для поиска и find_if при работе с вектором

#c #sorting #search

#c #сортировка #Поиск

Вопрос:

У меня есть структура, подобная этой:

 struct client
{
    string name;
    double money;
};
  

У меня также есть 2 предиката:

 bool less_10(const clientamp; a)
{
    return a.money < 10;
}

bool not_a(const clientamp; a)
{
    return a.name.at(0) != 'A';
}
  

В моей основной функции я использую это, чтобы отфильтровать результат, хранящийся в векторном client_list (все с деньгами < 10 (выбор 1) или все с именем, не начинающимся с A (else))

 if (choice_filter == 1)
    {
        vector<client>::iterator it3;
        it3 = find_if(client_list.begin(), client_list.end(), less_10);
        while (it3 != client_list.end())
        {
            **client_list.erase(it3); 
            it3 = find_if(it3   1, client_list.end(), less_10);
        }
        client_list.erase(it3);**
    }

    else
    {
        vector<client>::iterator it4;
        it4 = find_if(client_list.begin(), client_list.end(), not_a);

        while (it4 != client_list.end())
        {
            **client_list.erase(it4);
            it4 = find_if(it4   1, client_list.end(), not_a);
        }
        client_list.erase(it4);**
}
  

Я замечаю, что если я сначала удалю, а затем find_if , я потеряю последнего клиента. Итак, я добавил еще 1 строку для удаления, но программа вылетает, поскольку итератор теперь находится в конце, не может стереть.

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

Комментарии:

1. Используйте std::remove_if вместо этого и вызывайте erase , используя результат. Это колесо, которое вы изобретаете заново.

Ответ №1:

Как говорили другие, std::remove_if это лучшее решение. Если вы делаете это по педагогическим соображениям (что, как я подозреваю, имеет место, учитывая эти конкретные предикаты): вы на правильном пути. Единственная проблема заключается в том, что client_list.erase делает недействительным итератор. Но поскольку он возвращает итератор к элементу сразу после элемента, который он стер, вы можете использовать что-то вроде:

 std::vector<Client>::iterator it 
    = std::find_if( client_list.begin(), client_list.end(), predicate );
while ( it != client_list.end() ) {
    it = client_list.erase( it );
    it = std::find_if( it, client_list.end(), predicate );
}
  

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

Ответ №2:

Типичный способ — использовать временный вектор:

 vector<client> tmp;
for (...)
{
    if(predicate(it))
        tmp.push_back(*it);
}
client_list.swap(tmp);
  

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


Если вы использовали другой контейнер, такой как list<>, который не аннулировал все итераторы в erase(), вы могли бы сделать это:

 it = c.begin();
end = c.end();
while(it != end)
{
    if(predicate(*it))
    {
        c.erase(it  );
    }
    else
    {
          it;
    }
}
  

Обратите внимание, что если вы вызываете erase(), вы все равно аннулируете этот итератор, следовательно, итератор сначала увеличивается, а erase() вызывается с прежним значением, используя приращение postfix.

Ответ №3:

Я также согласен с Крисом в использовании std::remove_if:

 {
    remove_if(client_list.begin(), client_list.end(), less_10);
}
  

Но если вы хотите изобрести колесо:

 {
    vector<client>::iterator it3 = client_list.begin();
    while (true)
    {
        it3 = find_if(it3, client_list.end(), less_10);
        if (it3 == client_list.end()) {
            break;
        }
        it3 = client_list.erase(it3); 
    }
}
  

Комментарии:

1. Какой извращенный способ написания цикла. (И вы не объясняете, почему это решение не сработало.)