remove_if equivalente para std :: map


118

Eu estava tentando apagar uma série de elementos do mapa com base em uma condição particular. Como faço isso usando algoritmos STL?

Inicialmente pensei em usar, remove_ifmas não é possível, pois remove_if não funciona para container associativo.

Existe algum algoritmo equivalente "remove_if" que funciona para o mapa?

Como uma opção simples, pensei em percorrer o mapa e apagar. Mas repetir o mapa e apagar é uma opção segura? (Já que os iteradores ficam inválidos após o apagamento)

Usei o seguinte exemplo:

bool predicate(const std::pair<int,std::string>& x)
{
    return x.first > 2;
}

int main(void) 
{

    std::map<int, std::string> aMap;

    aMap[2] = "two";
    aMap[3] = "three";
    aMap[4] = "four";
    aMap[5] = "five";
    aMap[6] = "six";

//      does not work, an error
//  std::remove_if(aMap.begin(), aMap.end(), predicate);

    std::map<int, std::string>::iterator iter = aMap.begin();
    std::map<int, std::string>::iterator endIter = aMap.end();

    for(; iter != endIter; ++iter)
    {
            if(Some Condition)
            {
                            // is it safe ?
                aMap.erase(iter++);
            }
    }

    return 0;
}

O que quer dizer que remove_if não funciona?
dirkgently de

Não posso usar remove_if para encontrar um elemento no mapa, certo? Ele deu um erro de tempo de compilação. Estou esquecendo de algo?
aJ.

Não - não funciona porque remove_if funciona reordenando uma sequência, movendo os elementos que falham na condição para o final. Portanto, ele funciona em um T [n], mas não em um mapa <T, U>.
MSalters em

2
Com C + 11, você pode usar for(auto iter=aMap.begin(); iter!=aMap.end(); ){ ....}para reduzir a desordem. O descanso é como os outros disseram. Esta pergunta me poupou um pouco de confusão agora ;-)
Atul Kumar

Respostas:


111

Quase.

for(; iter != endIter; ) {
     if (Some Condition) {
          iter = aMap.erase(iter);
     } else {
          ++iter;
     }
}

O que você tinha originalmente incrementaria o iterador duas vezes se você apagasse um elemento dele; você poderia potencialmente pular elementos que precisavam ser apagados.

Este é um algoritmo comum que vi usado e documentado em muitos lugares.

[EDIT] Você está correto ao dizer que os iteradores são invalidados após um apagamento, mas apenas os iteradores que fazem referência ao elemento que foi apagado, outros iteradores ainda são válidos. Portanto, usando iter++na erase()chamada.


4
Estou confuso; por que você usaria para (; ...;) em vez de while (...)? Além disso, embora isso provavelmente funcione, .erase não retorna um iterador do próximo? Portanto, parece que o blog if (Some Condition) deveria ser iter = aMap.erase (iter) para ser o mais compatível. Talvez eu esteja faltando alguma coisa? Não tenho a experiência de alguns de vocês.
taxilian de

86
Observe que no C ++ 11 todos os contêineres associativos, incluindo map, retornam o próximo iterador de erase(iter). É muito mais limpo de fazer iter = erase( iter ).
Potatoswatter

10
@taxilian (anos de atraso) while () ou for () funcionaria, mas semanticamente, as pessoas costumam usar for () para iterar em um intervalo conhecido e while () para um número desconhecido de loops. Como o intervalo é conhecido neste caso (do início ao fimIter ), for () não seria uma escolha incomum e provavelmente seria mais comum. Mas, novamente, ambos seriam aceitáveis.
Jamin Gray de

4
@taxilian Mais importante: com 'for', você pode ter sua definição de iterador DENTRO do escopo do loop, para que não interfira com o resto do programa.
Sanchises

1
@athos A pergunta é formulada na voz passiva, "é recomendado." Não há recomendação universal. Acho que meu último comentário é a maneira mais direta. Envolve duas cópias da variável iteradora, que perde um pouco de eficiência, como alguém apontou aqui. É sua decisão o que é apropriado para você.
Potatoswatter

75

erase_if para std :: map (e outros contêineres)

Eu uso o seguinte modelo para isso.

namespace stuff {
  template< typename ContainerT, typename PredicateT >
  void erase_if( ContainerT& items, const PredicateT& predicate ) {
    for( auto it = items.begin(); it != items.end(); ) {
      if( predicate(*it) ) it = items.erase(it);
      else ++it;
    }
  }
}

Isso não retornará nada, mas removerá os itens do std :: map.

Exemplo de uso:

// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
  return /* insert appropriate test */;
});

Segundo exemplo (permite que você passe em um valor de teste):

// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4;  // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
  return item.property < test_value;  // or whatever appropriate test
});

3
@CodeAngry Obrigado - sempre me pareceu estranho que isso ainda não existisse no std. Eu entendo por que não é membro de std::map, mas acho que algo parecido deveria estar na biblioteca padrão.
Iron Savior

3
Será adicionado em C ++ 20 parastd::map e outros.
Roi Danton de


3

Eu obtive esta documentação da excelente referência SGI STL :

Map tem a propriedade importante de que inserir um novo elemento em um mapa não invalida os iteradores que apontam para os elementos existentes. Apagar um elemento de um mapa também não invalida nenhum iterador, exceto, é claro, para iteradores que realmente apontam para o elemento que está sendo apagado.

Portanto, o iterador que você possui que está apontando para o elemento a ser apagado, obviamente, será invalidado. Faça algo assim:

if (some condition)
{
  iterator here=iter++;
  aMap.erase(here)
}

3
Isso não é diferente do código original. iter ++ incrementa o iterador e então retorna um iterador apontando para o elemento antes do incremento.
Steve Folly,

Mas iter não será invalidado, uma vez que apagamos na posição aqui
1800 INFORMAÇÕES

@ 1800INFORMAÇÕES: inserir uma chamada de função é um ponto de sequência, o efeito colateral do incremento é avaliado antes de eraseser chamado. Portanto, eles são de fato equivalentes. Ainda assim, eu prefiro fortemente sua versão em vez da original.
Peterchen

Funciona para matriz ou vetor, mas causará um resultado inesperado no mapa de stl.
hunter_tech

2

O código original tem apenas um problema:

for(; iter != endIter; ++iter)
{
    if(Some Condition)
    {
        // is it safe ?
        aMap.erase(iter++);
    }
}

Aqui, o iteré incrementado uma vez no loop for e outra vez no erase, o que provavelmente terminará em algum loop infinito.


2

Aqui está uma solução elegante.

for (auto it = map.begin(); it != map.end();)
{   
    (SomeCondition) ? map.erase(it++) : (++it);
}

1

Das notas de fundo de:

http://www.sgi.com/tech/stl/PairAssociativeContainer.html

um Container Associativo de Par não pode fornecer iteradores mutáveis ​​(conforme definido nos requisitos do Trivial Iterator), porque o tipo de valor de um iterador mutável deve ser Atribuível e o par não é Atribuível. No entanto, um Container Associativo de Par pode fornecer iteradores que não são completamente constantes: iteradores de forma que a expressão (* i) .second = d seja válida.


1

Primeiro

Map tem a propriedade importante de que inserir um novo elemento em um mapa não invalida os iteradores que apontam para os elementos existentes. Apagar um elemento de um mapa também não invalida nenhum iterador, exceto, é claro, para iteradores que realmente apontam para o elemento que está sendo apagado.

Em segundo lugar, o código a seguir é bom

for(; iter != endIter; )
{
    if(Some Condition)
    {
        aMap.erase(iter++);
    }
    else
    {
        ++iter;
    }
}

Ao chamar uma função, os parâmetros são avaliados antes da chamada dessa função.

Portanto, quando o iter ++ é avaliado antes da chamada para apagar, o operador ++ do iterador retornará o item atual e apontará para o próximo item após a chamada.


1

IMHO não há remove_if()equivalente.
Você não pode reordenar um mapa.
Portanto, remove_if()não pode colocar seus pares de interesse no final em que você pode ligar erase().


Isso é realmente lamentável.
allyourcode

1

Baseado na resposta do Iron Savior Para aqueles que gostariam de fornecer uma gama mais próxima das linhas de iteradores de tomadas funcionais padrão.

template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
    for (; it != Last; ) {
        if (Pred(*it)) it = items.erase(it);
        else ++it;
    }
}

Curioso para saber se há alguma maneira de perder os ContainerTitens e obtê-los do iterador.


1
"Os identificadores que começam com um sublinhado seguido por uma letra maiúscula são reservados para todo uso pela implementação."
YSC

0

A resposta de Steve Folly Eu me sinto mais eficiente.

Aqui está outra solução fácil, mas menos eficiente :

A solução usa remove_copy_ifpara copiar os valores que queremos em um novo contêiner e, em seguida, trocar o conteúdo do contêiner original pelo novo:

std::map<int, std::string> aMap;

...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;

//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(), 
                    inserter(aTempMap, aTempMap.end()),
                    predicate);

//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);

2
Isso parece ineficiente.
allyourcode

0

Se você quiser apagar todos os elementos com chave maior que 2, a melhor maneira é

map.erase(map.upper_bound(2), map.end());

Porém, funciona apenas para intervalos, não para qualquer predicado.


0

Eu uso assim

 std::map<int, std::string> users;    
 for(auto it = users.begin(); it <= users.end()) {
    if(<condition>){
      it = users.erase(it);
    } else {
    ++it;
    }
 }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.