std :: map insert ou std :: map find?

90

Supondo um mapa onde você deseja preservar as entradas existentes. 20% do tempo, a entrada que você está inserindo são novos dados. Há uma vantagem em fazer std :: map :: find then std :: map :: insert usando esse iterador retornado? Ou é mais rápido tentar a inserção e agir com base no fato de o iterador indicar ou não que o registro foi ou não inserido?

Superpolock
fonte
4
Fui corrigido e pretendia usar std :: map :: lower_bound em vez de std :: map :: find.
Superpolock

Respostas:

147

A resposta é que você não faz nada. Em vez disso, você deseja fazer algo sugerido pelo Item 24 do STL Efetivo de Scott Meyers :

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}
Lucas
fonte
2
É realmente assim que find funciona, o truque é que isso combina a busca necessária para find e insert. Claro, o mesmo acontece apenas com o uso de insert e, em seguida, olhando para o segundo valor de retorno.
puetzk
1
Duas perguntas: 1) Como o uso de lower_bound difere de localizar para um mapa? 2) Para um 'mapa', não é o caso que a op do lado direito de && é sempre verdadeira quando 'lb! = Mymap.end ()'?
Richard Corden
11
@Richard: find () retorna end () se a chave não existe, lower_bound retorna a posição onde o item deveria estar (que por sua vez pode ser usado como dica de inserção). @puetzek: "apenas inserir" não substituiria o valor de referência das chaves existentes? Não tenho certeza se o OP deseja isso.
peterchen
2
alguém sabe se existe algo semelhante para unordered_map?
Giovanni Funchal
3
@peterchen map :: insert não substitui o valor existente se ele existir, consulte cplusplus.com/reference/map/map/insert .
Chris Drew
11

A resposta a esta pergunta também depende de quão caro é criar o tipo de valor que você está armazenando no mapa:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Para um tipo de valor como um int, o procedimento acima será mais eficiente do que um find seguido por uma inserção (na ausência de otimizações do compilador). Conforme afirmado acima, isso ocorre porque a pesquisa no mapa ocorre apenas uma vez.

No entanto, a chamada para inserir requer que você já tenha o novo "valor" construído:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.second->second = v;
  }
}

Para chamar de 'inserir', estamos pagando uma ligação cara para construir nosso tipo de valor - e pelo que você disse na pergunta, você não usará este novo valor 20% das vezes. No caso acima, se alterar o tipo de valor do mapa não for uma opção, é mais eficiente executar primeiro o 'localizar' para verificar se precisamos construir o elemento.

Como alternativa, o tipo de valor do mapa pode ser alterado para armazenar identificadores para os dados usando seu tipo de ponteiro inteligente favorito. A chamada para inserir usa um ponteiro nulo (muito barato de construir) e somente se necessário o novo tipo de dados é construído.

Richard Corden
fonte
8

Quase não haverá diferença de velocidade entre os 2, find retornará um iterador, insert faz o mesmo e pesquisará no mapa de qualquer maneira para determinar se a entrada já existe.

Então ... é uma questão de preferência pessoal. Eu sempre tento inserir e atualizar se necessário, mas algumas pessoas não gostam de lidar com o par que é retornado.

gbjbaanb
fonte
5

Eu acho que se você fizer um localizar e inserir, o custo extra seria quando você não encontrar a chave e executar a inserção depois. É como olhar os livros em ordem alfabética e não encontrar o livro, depois olhar os livros novamente para ver onde inseri-lo. Tudo se resume a como você manipulará as chaves e se elas estão mudando constantemente. Agora há alguma flexibilidade em que se você não encontrar, você pode registrar, exceção, fazer o que quiser ...

PiNoYBoY82
fonte
3

Estou perdido na melhor resposta.

Find retorna map.end () se não encontrar nada, o que significa que se você está adicionando coisas novas, então

iter = map.find();
if (iter == map.end()) {
  map.insert(..) or map[key] = value
} else {
  // do nothing. You said you did not want to effect existing stuff.
}

é duas vezes mais lento que

map.insert

para qualquer elemento que ainda não esteja no mapa, uma vez que terá que pesquisar duas vezes. Uma vez para ver se está lá, novamente para encontrar o lugar para colocar a coisa nova.

gman
fonte
1
Uma versão da inserção STL retorna um par contendo um iterador e um bool. O bool indica se foi encontrado ou não, o iterador é a entrada encontrada ou a entrada inserida. Isso é difícil de vencer em termos de eficiência; impossível, eu diria.
Zan Lynx
4
Não, a resposta marcada usada lower_bound, não find. Como resultado, se a chave não foi encontrada, ela retornou um iterador para o ponto de inserção, não o final. Como resultado, é mais rápido.
Steven Sudit
1

Se você está preocupado com a eficiência, pode verificar o hash_map <> .

Normalmente map <> é implementado como uma árvore binária. Dependendo de suas necessidades, um hash_map pode ser mais eficiente.

Adam Tegen
fonte
Teria adorado. Mas não há hash_map na biblioteca padrão C ++, e os PHBs não permitem código fora disso.
Superpolock
1
std :: tr1 :: unordered_map é o mapa hash proposto para ser adicionado ao próximo padrão e deve estar disponível na maioria das implementações atuais do STL.
beldaz
1

Eu não pareço ter pontos suficientes para deixar um comentário, mas a resposta marcada parece ser prolixa para mim - quando você considera que a inserção retorna o iterador de qualquer maneira, por que ir procurando lower_bound, quando você pode apenas usar o iterador retornado. Estranho.

Stonky
fonte
1
Porque (certamente pré-C ++ 11) usar insert significa que você ainda tem que criar um std::map::value_typeobjeto, a resposta aceita evita até isso.
KillianDS de
-1

Quaisquer respostas sobre eficiência dependerão da implementação exata de seu STL. A única maneira de saber com certeza é compará-lo de duas maneiras. Eu acho que a diferença provavelmente não será significativa, então decida com base no estilo de sua preferência.

Mark Ransom
fonte
1
isso não é exatamente verdade. A STL é diferente da maioria das outras bibliotecas porque fornece requisitos big-O explícitos para a maioria de suas operações. Há uma diferença garantida entre 2 * O (log n) e 1 * O (log n), independentemente de qual implementação as funções usam para atingir esse comportamento O (log n). Se essa diferença é significativa ou não em sua plataforma, é outra questão. Mas a diferença sempre estará lá.
srm
@srm que define os requisitos do big-O ainda não diz quanto tempo uma operação levará em termos absolutos. A diferença garantida de que você fala não existe.
Mark Ransom
-2

map [tecla] - deixe stl resolver isso. Isso é comunicar sua intenção de maneira mais eficaz.

Sim, é justo.

Se você fizer um find e, em seguida, um insert, você está executando 2 x O (log N) quando errar, pois o find apenas permite que você saiba se você não precisa inserir onde o insert deve ir (lower_bound pode ajudá-lo nisso) . Apenas uma inserção direta e, em seguida, examinar o resultado é o caminho que eu faria.


fonte
Não, se a entrada existir, ele retornará uma referência à entrada existente.
Kris Kumler
2
-1 para esta resposta. Como Kris K disse, usar map [key] = value sobrescreverá a entrada existente, não a "preservará" conforme exigido na pergunta. Você não pode testar a existência usando map [chave], porque ele retornará um objeto padrão construído se a chave não existir, e o criará como a entrada para a chave
netjeff 01 de
O objetivo é testar se o mapa já está preenchido e apenas adicionar / sobrescrever se não estiver lá. Usar map [chave] assume que o valor já está lá sempre.
srm