Eu preciso pegar um vetor C ++ com potencialmente muitos elementos, apagar duplicatas e classificá-lo.
Atualmente, tenho o código abaixo, mas ele não funciona.
vec.erase(
std::unique(vec.begin(), vec.end()),
vec.end());
std::sort(vec.begin(), vec.end());
Como posso fazer isso corretamente?
Além disso, é mais rápido apagar as duplicatas primeiro (semelhante ao codificado acima) ou executar a classificação primeiro? Se eu executar a classificação primeiro, é garantido que permaneça classificada após a std::unique
execução?
Ou existe outra maneira (talvez mais eficiente) de fazer tudo isso?
Respostas:
Eu concordo com R. Pate e Todd Gardner ; uma
std::set
pode ser uma boa ideia aqui. Mesmo se você estiver preso ao usar vetores, se você tiver duplicatas suficientes, é melhor criar um conjunto para fazer o trabalho sujo.Vamos comparar três abordagens:
Apenas usando vetor, classificar + exclusivo
Converter para definir (manualmente)
Converter em conjunto (usando um construtor)
Aqui está como elas são executadas conforme o número de duplicatas é alterado:
Resumo : quando o número de duplicatas é grande o suficiente, é realmente mais rápido converter em um conjunto e depois despejar os dados em um vetor .
E, por alguma razão, fazer a conversão do conjunto manualmente parece ser mais rápido do que usar o construtor do conjunto - pelo menos nos dados aleatórios de brinquedo que eu usei.
fonte
Refiz o perfil de Nate Kohl e obtive resultados diferentes. Para o meu caso de teste, classificar diretamente o vetor é sempre mais eficiente do que usar um conjunto. Eu adicionei um novo método mais eficiente, usando um
unordered_set
.Lembre-se de que o
unordered_set
método só funciona se você tiver uma boa função de hash para o tipo de unificação e classificação necessárias. Para ints, isso é fácil! (A biblioteca padrão fornece um hash padrão, que é simplesmente a função de identidade.) Além disso, não se esqueça de classificar no final, pois unordered_set é, bem, desordenado :)Eu fiz alguma escavação dentro do
set
eunordered_set
implementação e descobriu que o construtor realmente construir um novo nó para cada elemento, antes de verificar o seu valor para determinar se ele realmente deve ser inserido (na implementação do Visual Studio, pelo menos).Aqui estão os 5 métodos:
f1: Apenas usando
vector
,sort
+unique
f2: converta para
set
(usando um construtor)f3: converter para
set
(manualmente)f4: converta para
unordered_set
(usando um construtor)f5: converter para
unordered_set
(manualmente)Eu fiz o teste com um vetor de 100.000.000 de ints escolhido aleatoriamente nos intervalos [1,10], [1.100] e [1.100000]
Os resultados (em segundos, menor é melhor):
fonte
sort
ouunique
métodos, você tem que#include <algorithm>
CWUK
cenário que tem natureza de propablities para desacelerar oemplace
tipo de construção.std::unique
somente remove elementos duplicados se forem vizinhos: você deve classificar o vetor primeiro antes que ele funcione conforme pretendido.std::unique
é definido como estável, portanto, o vetor ainda será classificado após a execução exclusiva nele.fonte
Não sei bem para que você está usando isso, então não posso dizer isso com 100% de certeza, mas normalmente quando penso em um contêiner "classificado e único", penso em um std :: set . Pode ser um ajuste melhor para o seu caso de usuário:
Caso contrário, a classificação anterior à chamada única (como as outras respostas apontam) é o caminho a percorrer.
fonte
std::unique
funciona apenas em execuções consecutivas de elementos duplicados, então é melhor classificar primeiro. No entanto, como é estável, seu vetor permanecerá classificado.fonte
Aqui está um modelo para fazer isso por você:
chame assim:
fonte
erase()
método, caso contrário, você deve retornar o novo iterador final e o código de chamada truncará o contêiner.Eficiência é um conceito complicado. Há considerações de tempo versus espaço, bem como medições gerais (onde você só obtém respostas vagas, como O (n)) versus específicas (por exemplo, a classificação de bolhas pode ser muito mais rápida que a classificação rápida, dependendo das características da entrada).
Se você tiver relativamente poucas duplicatas, a classificação seguida por única e a exclusão parecerão o caminho a seguir. Se você tiver relativamente muitas duplicatas, criar um conjunto a partir do vetor e deixá-lo fazer o trabalho pesado pode vencê-lo facilmente.
Não se concentre apenas na eficiência do tempo. Ordenar + exclusivo + apagar opera no espaço O (1), enquanto a construção do conjunto opera no espaço O (n). E nenhum deles se presta diretamente a uma paralelização de redução de mapa (para conjuntos de dados realmente grandes ).
fonte
Você precisa classificá-lo antes de ligar,
unique
porqueunique
apenas remove duplicatas próximas umas das outras.editar: 38 segundos ...
fonte
unique
remove apenas elementos duplicados consecutivos (o que é necessário para ser executado em tempo linear), portanto, você deve executar a classificação primeiro. Ele permanecerá classificado após a chamada paraunique
.fonte
Se você não deseja alterar a ordem dos elementos, tente esta solução:
fonte
Supondo que a seja um vetor, remova as duplicatas contíguas usando
a.erase(unique(a.begin(),a.end()),a.end());
é executado em O (n) tempo.fonte
std::sort
primeiro.Como já foi dito,
unique
requer um contêiner classificado. Além disso,unique
na verdade , não remove elementos do contêiner. Em vez disso, eles são copiados para o final,unique
retorna um iterador apontando para o primeiro elemento duplicado e você deve chamarerase
para remover os elementos.fonte
A abordagem padrão sugerida por Nate Kohl, apenas usando vetor, classifica + exclusivo:
não funciona para um vetor de ponteiros.
Veja atentamente este exemplo em cplusplus.com .
No exemplo deles, as "chamadas duplicatas" movidas para o final são realmente mostradas como? (valores indefinidos), porque essas "chamadas duplicatas" são ALGUMAS VEZES "elementos extras" e ALGUMAS VEZES existem "elementos ausentes" que estavam no vetor original.
Ocorre um problema ao usar
std::unique()
um vetor de ponteiros para objetos (vazamentos de memória, leitura incorreta de dados do HEAP, liberações duplicadas, que causam falhas de segmentação etc.).Aqui está a minha solução para o problema: substitua
std::unique()
porptgi::unique()
.Veja o arquivo ptgi_unique.hpp abaixo:
E aqui está o programa UNIT Test que eu usei para testá-lo:
fonte
std::unique
você ter [1, 2, 3, 2], você não pode chamar delete em 2, pois isso deixaria um ponteiro para 2! => Simplesmente não chame delete nos elementos entrenewEnd = std::unique
estd::end
como você ainda possui ponteiros para esses elementos[std::begin, newEnd)
!unique
avector<unique_ptr<T>>
, pois o único valor duplicado que esse vetor pode conter énullptr
.Com a biblioteca Ranges (disponível em C ++ 20), você pode simplesmente usar
Observe que ele realmente remove os elementos duplicados, não apenas os move.
fonte
Sobre os benchmarks alexK7. Eu tentei e obtive resultados semelhantes, mas quando o intervalo de valores é de 1 milhão, os casos usando std :: sort (f1) e std :: unordered_set (f5) produzem tempo semelhante. Quando o intervalo de valores é 10 milhões, f1 é mais rápido que f5.
Se o intervalo de valores for limitado e os valores não tiverem sinal int, é possível usar std :: vector, cujo tamanho corresponde ao intervalo especificado. Aqui está o código:
fonte
ordenar (v.begin (), v.end ()), v.erase (único (v.begin (), v, end ()), v.end ());
fonte
Se você estiver procurando desempenho e uso
std::vector
, recomendo o que este link de documentação fornece.fonte
fonte
Se você não quiser modificar o vetor (apagar, classificar), poderá usar a biblioteca Newton . Na sub- biblioteca de algoritmos, há uma chamada de função, copy_single
então você pode:
onde cópia é o vetor no qual você deseja enviar a cópia dos elementos exclusivos. mas lembre-se de empurrar os elementos para trás e não criar um novo vetor
de qualquer forma, isso é mais rápido porque você não apaga () os elementos (o que leva muito tempo, exceto quando você pop_back (), devido à reatribuição)
Eu faço alguns experimentos e é mais rápido.
Além disso, você pode usar:
às vezes ainda é mais rápido.
fonte
unique_copy
.Código mais compreensível em: https://en.cppreference.com/w/cpp/algorithm/unique
saída:
fonte
fonte
Aqui está o exemplo do problema de exclusão duplicada que ocorre com std :: unique (). Em uma máquina LINUX, o programa falha. Leia os comentários para obter detalhes.
fonte
vector
contém números inteiros, não ponteiros e não especifica um comparador).Esta é uma função que eu criei que você pode usar para excluir repetições. Os arquivos de cabeçalho necessários são justos
<iostream>
e<vector>
.fonte