J'ai besoin de prendre un vecteur C ++ avec potentiellement beaucoup d'éléments, d'effacer les doublons et de le trier.
J'ai actuellement le code ci-dessous, mais cela ne fonctionne pas.
vec.erase(
std::unique(vec.begin(), vec.end()),
vec.end());
std::sort(vec.begin(), vec.end());
Comment puis-je le faire correctement?
De plus, est-il plus rapide d'effacer les doublons en premier (similaire au code ci-dessus) ou d'effectuer le tri en premier? Si j'effectue d'abord le tri, est-il garanti de rester trié après std::unique
son exécution?
Ou existe-t-il une autre façon (peut-être plus efficace) de faire tout cela?
Réponses:
Je suis d'accord avec R. Pate et Todd Gardner ; un
std::set
pourrait être une bonne idée ici. Même si vous êtes bloqué à l'aide de vecteurs, si vous avez suffisamment de doublons, vous feriez mieux de créer un ensemble pour faire le sale boulot.Comparons trois approches:
En utilisant simplement le vecteur, trier + unique
Convertir en ensemble (manuellement)
Convertir en ensemble (à l'aide d'un constructeur)
Voici comment ceux-ci fonctionnent lorsque le nombre de doublons change:
Résumé : lorsque le nombre de doublons est suffisamment important, il est en fait plus rapide de convertir en un ensemble, puis de sauvegarder les données dans un vecteur .
Et pour une raison quelconque, faire la conversion d'ensemble manuellement semble être plus rapide que d'utiliser le constructeur d'ensemble - au moins sur les données aléatoires de jouets que j'ai utilisées.
la source
J'ai refait le profilage de Nate Kohl et j'ai obtenu des résultats différents. Pour mon cas de test, trier directement le vecteur est toujours plus efficace que d'utiliser un ensemble. J'ai ajouté une nouvelle méthode plus efficace, en utilisant un
unordered_set
.Gardez à l'esprit que la
unordered_set
méthode ne fonctionne que si vous avez une bonne fonction de hachage pour le type dont vous avez besoin unique et trié. Pour les pouces, c'est facile! (La bibliothèque standard fournit un hachage par défaut qui est simplement la fonction d'identité.) N'oubliez pas non plus de trier à la fin car unordered_set est, eh bien, non ordonné :)Je l' ai fait quelques recherches dans la
set
et launordered_set
mise en œuvre et a découvert que le constructeur construction en fait un nouveau noeud pour chaque élément, avant de vérifier sa valeur pour déterminer si elle doit effectivement être inséré (dans la mise en œuvre Visual Studio, au moins).Voici les 5 méthodes:
f1: Il suffit d'utiliser
vector
,sort
+unique
f2: Convertir en
set
(à l'aide d'un constructeur)f3: Convertir en
set
(manuellement)f4: convertir en
unordered_set
(à l'aide d'un constructeur)f5: Convertir en
unordered_set
(manuellement)J'ai fait le test avec un vecteur de 100 000 000 d'entités choisies au hasard dans les plages [1,10], [1,1000] et [1,100000]
Les résultats (en quelques secondes, plus petit est meilleur):
la source
sort
unique
#include <algorithm>
CWUK
scénario qui a une nature de propriétés pour ralentir leemplace
type de construction.std::unique
ne supprime les éléments en double que s'ils sont voisins: vous devez d'abord trier le vecteur avant qu'il ne fonctionne comme vous le souhaitez.std::unique
est défini comme étant stable, donc le vecteur sera toujours trié après avoir été exécuté sur lui.la source
Je ne sais pas à quoi vous l'utilisez, donc je ne peux pas le dire avec 100% de certitude, mais normalement quand je pense à un conteneur "trié, unique", je pense à un std :: set . Cela pourrait être mieux adapté à votre cas d'utilisation:
Sinon, le tri avant d'appeler unique (comme l'ont souligné les autres réponses) est la voie à suivre.
la source
std::unique
ne fonctionne que sur des exécutions consécutives d'éléments en double, vous devriez donc d'abord trier. Cependant, il est stable, donc votre vecteur restera trié.la source
Voici un modèle pour le faire pour vous:
appelez-le comme:
la source
erase()
méthode, sinon vous devez renvoyer le nouvel itérateur de fin et demander au code appelant de tronquer le conteneur.L'efficacité est un concept compliqué. Il y a des considérations de temps par rapport à l'espace, ainsi que des mesures générales (où vous n'obtenez que des réponses vagues telles que O (n)) et des réponses spécifiques (par exemple, le tri à bulles peut être beaucoup plus rapide que le tri rapide, selon les caractéristiques d'entrée).
Si vous avez relativement peu de doublons, le tri suivi de l'unique et l'effacement semblent être la solution. Si vous aviez relativement plusieurs doublons, créer un ensemble à partir du vecteur et le laisser faire le gros du travail pourrait facilement le battre.
Ne vous concentrez pas seulement sur l'efficacité du temps. Tri + unique + effacement fonctionne dans l'espace O (1), tandis que la construction d'ensemble fonctionne dans l'espace O (n). Et ni l'un ni l'autre ne se prête directement à une parallélisation de réduction de carte (pour des ensembles de données vraiment énormes ).
la source
Vous devez le trier avant d'appeler,
unique
carunique
il supprime uniquement les doublons les uns à côté des autres.montage: 38 secondes ...
la source
unique
supprime uniquement les éléments en double consécutifs (ce qui est nécessaire pour qu'il s'exécute en temps linéaire), vous devez donc effectuer le tri en premier. Il restera trié après l'appel àunique
.la source
Si vous ne souhaitez pas modifier l'ordre des éléments, vous pouvez essayer cette solution:
la source
En supposant que a est un vecteur, supprimez les doublons contigus à l'aide de
a.erase(unique(a.begin(),a.end()),a.end());
s'exécute en temps O (n) .la source
std::sort
première.Comme déjà indiqué,
unique
nécessite un conteneur trié. De plus,unique
ne supprime pas réellement les éléments du conteneur. Au lieu de cela, ils sont copiés à la fin,unique
renvoie un itérateur pointant vers le premier élément en double de ce type, et vous êtes censé appelererase
pour supprimer réellement les éléments.la source
L'approche standard suggérée par Nate Kohl, en utilisant simplement vector, sort + unique:
ne fonctionne pas pour un vecteur de pointeurs.
Regardez attentivement cet exemple sur cplusplus.com .
Dans leur exemple, les «soi-disant doublons» déplacés à la fin sont en fait représentés par? (valeurs indéfinies), car ces "soi-disant doublons" sont PARFOIS "éléments supplémentaires" et PARFOIS il y a des "éléments manquants" qui étaient dans le vecteur d'origine.
Un problème survient lors de l'utilisation
std::unique()
sur un vecteur de pointeurs vers des objets (fuites de mémoire, mauvaise lecture des données de HEAP, libérations de doublons, qui provoquent des défauts de segmentation, etc.).Voici ma solution au problème: remplacer
std::unique()
parptgi::unique()
.Voir le fichier ptgi_unique.hpp ci-dessous:
Et voici le programme UNIT Test que j'ai utilisé pour le tester:
la source
std::unique
eu [1, 2, 3, 2], vous ne pouvez pas appeler la suppression sur 2 car cela laisserait un pointeur suspendu à 2! => N'appelez simplement pas delete sur les éléments entrenewEnd = std::unique
etstd::end
car vous avez toujours des pointeurs vers ces éléments dans[std::begin, newEnd)
!unique
avector<unique_ptr<T>>
, car la seule valeur dupliquée qu'un tel vecteur puisse contenir estnullptr
.Avec la bibliothèque Ranges (disponible en C ++ 20), vous pouvez simplement utiliser
Notez qu'il supprime en fait les éléments en double, pas seulement les déplacer.
la source
À propos des benchmarks alexK7. Je les ai essayés et j'ai obtenu des résultats similaires, mais lorsque la plage de valeurs est de 1 million, les cas utilisant std :: sort (f1) et utilisant std :: unordered_set (f5) produisent un temps similaire. Lorsque la plage de valeurs est de 10 millions, f1 est plus rapide que f5.
Si la plage de valeurs est limitée et que les valeurs ne sont pas signées int, il est possible d'utiliser std :: vector, dont la taille correspond à la plage donnée. Voici le code:
la source
sort (v.begin (), v.end ()), v.erase (unique (v.begin (), v, end ()), v.end ());
la source
Si vous recherchez des performances et une utilisation
std::vector
, je recommande celle fournie par ce lien de documentation .la source
la source
Si vous ne voulez pas modifier le vecteur (effacer, trier), vous pouvez utiliser la bibliothèque Newton , dans la sous- bibliothèque d' algorithmes il y a un appel de fonction, copy_single
afin que vous puissiez:
où copie est le vecteur dans lequel vous souhaitez repousser la copie des éléments uniques. mais rappelez-vous que vous repoussez les éléments et que vous ne créez pas de nouveau vecteur
de toute façon, c'est plus rapide car vous n'effacez pas () les éléments (ce qui prend beaucoup de temps, sauf lorsque vous pop_back (), à cause de la réaffectation)
Je fais des expériences et c'est plus rapide.
Vous pouvez également utiliser:
est parfois encore plus rapide.
la source
unique_copy
.Code plus compréhensible depuis: https://en.cppreference.com/w/cpp/algorithm/unique
sortie:
la source
la source
Voici l'exemple du problème de suppression en double qui se produit avec std :: unique (). Sur une machine LINUX, le programme plante. Lisez les commentaires pour plus de détails.
la source
vector
contient des entiers, pas des pointeurs, et ne spécifie pas de comparateur).Il s'agit d'une fonction que j'ai créée que vous pouvez utiliser pour supprimer les répétitions. Les fichiers d'en-tête nécessaires sont juste
<iostream>
et<vector>
.la source