web-dev-qa-db-fra.com

Un moyen plus élégant de vérifier les doublons dans un tableau C++?

J'ai écrit ce code en C++ dans le cadre d'une tâche uni où je dois m'assurer qu'il n'y a pas de doublons dans un tableau:

// Check for duplicate numbers in user inputted data
    int i; // Need to declare i here so that it can be accessed by the 'inner' loop that starts on line 21
    for(i = 0;i < 6; i++) { // Check each other number in the array
        for(int j = i; j < 6; j++) { // Check the rest of the numbers
            if(j != i) { // Makes sure don't check number against itself
                if(userNumbers[i] == userNumbers[j]) {
                    b = true;
                }
            }
            if(b == true) { // If there is a duplicate, change that particular number
                cout << "Please re-enter number " << i + 1 << ". Duplicate numbers are not allowed:" << endl;
                cin >> userNumbers[i];
            }
        } // Comparison loop
        b = false; // Reset the boolean after each number entered has been checked
    } // Main check loop

Cela fonctionne parfaitement, mais j'aimerais savoir s'il existe un moyen de vérification plus élégant ou plus efficace.

16
Saladin Akara

Vous pouvez trier le tableau dans O (nlog (n)), puis simplement regarder jusqu'au nombre suivant. C'est nettement plus rapide que votre algorithme existant O (n ^ 2). Le code est également beaucoup plus propre. Votre code ne garantit pas non plus qu'aucun doublon n'a été inséré lors de la nouvelle saisie. Vous devez empêcher les doublons d'exister en premier lieu.

std::sort(userNumbers.begin(), userNumbers.end());
for(int i = 0; i < userNumbers.size() - 1; i++) {
    if (userNumbers[i] == userNumbers[i + 1]) {
        userNumbers.erase(userNumbers.begin() + i);
        i--;
    }
}

J'approuve également la recommandation d'utiliser un std :: set - pas de doublons.

19
Puppy

La solution suivante consiste à trier les nombres, puis à supprimer les doublons:

#include <algorithm>

int main()
{
    int userNumbers[6];

    // ...

    int* end = userNumbers + 6;
    std::sort(userNumbers, end);
    bool containsDuplicates = (std::unique(userNumbers, end) != end);
}
9
fredoverflow

En effet, la méthode la plus élégante et la plus élégante que je connaisse est celle conseillée ci-dessus:

std::vector<int> tUserNumbers;
// ...
std::set<int> tSet(tUserNumbers.begin(), tUserNumbers.end());
std::vector<int>(tSet.begin(), tSet.end()).swap(tUserNumbers);

C'est O (n log n). Cela ne le fait toutefois pas, si l'ordre des nombres dans le tableau d'entrée doit être conservé ... Dans ce cas, j'ai fait:

    std::set<int> tTmp;
    std::vector<int>::iterator tNewEnd = 
        std::remove_if(tUserNumbers.begin(), tUserNumbers.end(), 
        [&tTmp] (int pNumber) -> bool {
            return (!tTmp.insert(pNumber).second);
    });
    tUserNumbers.erase(tNewEnd, tUserNumbers.end());

qui est toujours O (n log n) et conserve la commande d'origine des éléments dans tUserNumbers

À votre santé,

Paul

7
Paul Michalik

Vous pouvez ajouter tous les éléments d'un ensemble et vérifier lors de l'ajout s'il est déjà présent ou non. Ce serait plus élégant et efficace. 

6
Benoit Thiery

Je ne sais pas pourquoi cela n’a pas été suggéré, mais voici qu’il existe un moyen de trouver des doublons dans O (n) en base 10. Le problème que je vois avec la solution O(n) déjà suggérée est qu'elle nécessite que les chiffres soient d'abord triés. Cette méthode est O(n) et ne nécessite pas le tri de l'ensemble. La chose intéressante est que vérifier si un chiffre spécifique a des doublons est O (1). Je sais que ce fil est probablement mort, mais peut-être que cela aidera quelqu'un! :)

/*
============================
Foo
============================
* 
   Takes in a read only unsigned int. A table is created to store counters 
   for each digit. If any digit's counter is flipped higher than 1, function
   returns. For example, with 48778584:
    0   1   2   3   4   5   6   7   8   9
   [0] [0] [0] [0] [2] [1] [0] [2] [2] [0]

   When we iterate over this array, we find that 4 is duplicated and immediately
   return false.

*/
bool Foo( unsigned const int &number)
{
    int temp = number;
    int digitTable[10]={0};

    while(temp > 0)
    {
        digitTable[temp % 10]++; // Last digit's respective index.
        temp /= 10; // Move to next digit
    }

    for (int i=0; i < 10; i++)
    {
        if (digitTable [i] > 1)
        {
            return false;
        }
    }
    return true;
}
5
Josh Sanders

Il est en extension de la réponse de @Puppy, qui est actuellement la meilleure réponse.

PS: J'ai essayé d'insérer ce message en tant que commentaire dans la meilleure réponse actuelle de @Puppy, mais je n'ai pas pu, car je n'ai pas encore 50 points. Un peu de données expérimentales est également partagé ici pour une aide supplémentaire. 

Std :: set et std :: map sont tous deux implémentés dans STL en utilisant uniquement l'arborescence Recherche binaire équilibrée. Ainsi, les deux entraîneront une complexité de O(nlogn) uniquement dans ce cas. Les meilleures performances peuvent être obtenues si une table de hachage est utilisée. std :: unordered_map offre une implémentation basée sur une table de hachage pour une recherche plus rapide. J'ai essayé les trois implémentations et trouvé que les résultats obtenus avec std :: unordered_map étaient meilleurs que std :: set et std :: map. Les résultats et le code sont partagés ci-dessous. Les images sont un instantané des performances mesurées par LeetCode sur les solutions.

bool hasDuplicate(vector<int>& nums) {
    size_t count = nums.size();
    if (!count)
        return false;
    std::unordered_map<int, int> tbl;
    //std::set<int> tbl;
    for (size_t i = 0; i < count; i++) {
        if (tbl.find(nums[i]) != tbl.end())
            return true;
        tbl[nums[i]] = 1;
        //tbl.insert(nums[i]);
    }
    return false;
}
 </ pre> 

</ code> unordered_map Performances (La durée d'exécution est ici à 52 ms)  enter image description here  

Set/Map Performance  enter image description here  

5
ViFI

C'est bon, spécialement pour les petites longueurs de tableau. J'utiliserais des approches plus efficaces (moins de n ^ 2/2 comparaisons) si le tableau est beaucoup plus grand - voir la réponse de DeadMG.

Quelques petites corrections pour votre code: 

  • Au lieu de int j = i writeint j = i +1 et vous pouvez omettre votre test if(j != i)
  • Vous ne devriez pas avoir besoin de déclarer la variable i en dehors de l'instruction for.
3
leonbloy
#include<iostream>
#include<algorithm>

int main(){

    int arr[] = {3, 2, 3, 4, 1, 5, 5, 5};
    int len = sizeof(arr) / sizeof(*arr); // Finding length of array

    std::sort(arr, arr+len);

    int unique_elements = std::unique(arr, arr+len) - arr;

    if(unique_elements == len) std::cout << "Duplicate number is not present here\n";
    else std::cout << "Duplicate number present in this array\n";

    return 0;
}
1
Fake_Death
//std::unique(_copy) requires a sorted container.
std::sort(cont.begin(), cont.end());

//testing if cont has duplicates
std::unique(cont.begin(), cont.end()) != cont.end();

//getting a new container with no duplicates
std::unique_copy(cont.begin(), cont.end(), std::back_inserter(cont2));
1
coh