1
votes

Comment parcourir des vecteurs pour des chaînes spécifiques

J'ai du mal à déclarer une boucle qui prend un champ d'un vecteur, vérifier s'il apparaît pour la première fois ou passer au vecteur suivant jusqu'à ce que ce champ contienne une nouvelle chaîne.

Mon fichier d'entrée (.csvx) ressemble à quelque chose comme:

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <boost/algorithm/string.hpp>

using namespace std;

/*
 * CSVX Reader defined to fetch data from 
 * CSVX file into vectors
 */
class CSVXReader
{
   string fileName, delimiter;
public:
   CSVXReader(string filename, string delm = ";") :
   fileName(filename), delimiter(delm)
   {}
   vector<vector<string> > getData();           //Function to fetch data 
   };                                           //from CSVX file 

/*
 * Parse through CSVX file line by line 
 * and return the data in vector of vector
 * of strings
 */
vector<vector<string> > CSVXReader::getData()
{
   ifstream file(fileName);
   vector<vector<string> > dataList;               //Vector of vector 
                                                   //contains all data

   string line = "";                              
   while (getline(file, line))                  //Iterate through each line 
                                                //and split the content 
                                                //using delimiter
   {
      vector<string> vec;                       //Vector contains a row from 
                                                //input file 
      boost::algorithm::split(vec, line, boost::is_any_of(delimiter));
      dataList.push_back(vec);
   }
file.close();
return dataList;
}


int main(int argc, char** argv) 
{
   CSVXReader reader("file.csvx");                     //Creating an object 
                                                       //of CSVXReader
   vector<vector<string> > dataList = reader.getData();//Get the data from 
                                                       //CSVX file
   for(vector<string> vec : datalist)                  //Loop to go through 
                                                       //each line of 
                                                       //dataList 
                                                       //(vec1,vec2;vec3...)
   if(vec[1] contains "_" && "appears for the first time")
   {store parameters...};
   else{go to next line};
return 0;
}

Remarque: Le fichier est relativement volumineux ....

J'ai réussi à analyser mon fichier dans un vecteur > et séparez les lignes au point-virgule pour accéder à n'importe quel champ. Je voudrais maintenant accéder au premier "ID", c'est-à-dire 1_380 et stocker les paramètres de la même ligne, puis passer à l'ID suivant 2_380 et stocker à nouveau ces paramètres et ainsi de suite ...

Ceci est mon code jusqu'ici:

No.; ID; A; B; C;...;Z;
1;1_380; Value; Value; Value;...; Value;
2;1_380; Value; Value; Value;...; Value;
3;1_380; Value; Value; Value;...; Value;
...
41;2_380; Value; Value; Value;...; Value;
42;2_380; Value; Value; Value;...; Value;
...
400000; 6_392; Value; Value; Value;...; Value; 

Comme vous pouvez le voir, je n'ai aucune idée de comment déclarer ma boucle correctement ... Pour être clair, je veux vérifier le deuxième champ de chaque vecteur "vec": est-il nouveau? -> Stocker les données de la même ligne, sinon -> passer à la ligne suivante, c'est-à-dire au vecteur jusqu'à ce qu'un nouvel identifiant apparaisse.

Dans l'attente de tout conseil!


5 commentaires

Quelque part, vous devriez vraiment utiliser un std :: unordered_set pour enregistrer les doublons et / ou aider à détecter les doublons.


Je ne sais pas si cela est applicable dans votre cas, mais j'importerais des données dans la base de données, disons sqlite, et travaillerais avec l'API de base de données standard.


Ou au moins utiliser une bibliothèque csv existante (comme libcsv par exemple).


@sklott Je préfère ne travailler qu'avec un seul script C ++ ...


@SanderDeDycker devra d'abord se pencher sur cela, mais merci pour votre indice.


3 Réponses :


3
votes

Depuis que vous avez écrit du pseudo-code, il est difficile d'écrire du vrai code.

Mais en général, si vous souhaitez détecter si un élément s'est déjà produit, vous pouvez utiliser un std :: unordered_set pour implémenter le" apparaît pour la première fois ".

Utilisation de votre pseudo-code:

#include <unordered_set>
//...
std::unordered_set<std::string> stringSet;
//...
for(vector<string>& vec : datalist)
{
    if(vec[1] contains "_" && !stringSet.count(vec[1]))
    {
         //...
         stringSet.insert(vec[1]);
    }
}

La condition vérifie si l'élément est dans unordered_set. Si c'est le cas, alors nous sautons, sinon, nous traitons l'élément et l'ajoutons à unordered_set.


2 commentaires

Votre boucle for fait une copie inutile de chaque vecteur et vous faites également 2 recherches dans unordered_set inutiles.


En fait, mon code est réel sauf pour les 5 dernières lignes ... mais il peut être trash quand même;) Cela semble être une solution très prometteuse, merci!



1
votes

En gros, vous n'avez pas besoin de tout le code fourni par les autres réponses. Vous n'avez besoin que d'une seule instruction pour copier les données là où vous voulez les avoir.

Supposons que vous ayez déjà lu vos données dans votre dataList . Et vous avez défini un nouveau paramètre std :: vector > {}; où vous souhaitez stocker le résultat unique.

L'algorithme libraray a une fonction appelée std: copy_if . Cela ne copiera les données que si un prédicat (une condition) est vrai. Votre condition est qu'une ligne est différente d'une ligne précédente. Ensuite, c'est une nouvelle ligne avec de nouvelles données et vous la copiez. Si une ligne est égale à ses données de ligne précédentes, ne la copiez pas.

Donc, nous nous souviendrons des données importantes de la dernière ligne. Et puis comparez dans la ligne suivante les données avec la valeur stockée. S'il est différent, enregistrez le paramètre. Sinon, alors non. Après chaque vérification, nous attribuons la valeur actuelle à la dernière valeur. En tant que «dernière valeur» initiale, nous utiliserons une chaîne vide. La première ligne sera donc toujours différente. L'instruction ressemblera alors à ceci:

#include <vector>
#include <iostream>
#include <string>
#include <iterator>
#include <regex>
#include <fstream>
#include <sstream>
#include <algorithm>

std::istringstream testFile{R"(1;1_380; Value1; Value2; Value3; Value4
2;1_380; Value5; Value6; Value7; Value8
3;1_380; Value9 Value10 
41;2_380; Value11; Value12; Value13
42;2_380; Value15
42;2_380; Value16
500;3_380; Value99
400000; 6_392; Value17; Value18; Value19; Value20
400001; 6_392; Value21; Value22; Value23; Value24)"
};


class LineAsVector {    // Proxy for the input Iterator
public:
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, LineAsVector& lv) {

        // Read a line
        std::string line; lv.completeLine.clear();
        std::getline(is, line); 

        // The delimiter
        const std::regex re(";");

        // Split values and copy into resulting vector
        std::copy(  std::sregex_token_iterator(line.begin(), line.end(), re, -1),
                    std::sregex_token_iterator(),
                    std::back_inserter(lv.completeLine));
        return is; 
    }

    // Cast the type 'CompleteLine' to std::string
    operator std::vector<std::string>() const { return completeLine; }
protected:
    // Temporary to hold the read vector
    std::vector<std::string> completeLine{};
};

int main()
{

    // This is the resulting vector which will contain the result
    std::vector<std::vector<std::string>> parameter{};


    // One copy statement to copy all necessary data from the file to the parameter list
    std::copy_if (
        std::istream_iterator<LineAsVector>(testFile),
        std::istream_iterator<LineAsVector>(),
        std::back_inserter(parameter),
        [lastID = std::string{}](const std::vector<std::string> & sv) mutable {
            bool result = (lastID != sv[1]);
            lastID = sv[1];
            return result;
        }
    );


    // For debug purposes: Show result on screen
    std::for_each(parameter.begin(), parameter.end(), [](std::vector<std::string> & sv) {
        std::copy(sv.begin(), sv.end(), std::ostream_iterator<std::string>(std::cout, " "));
        std::cout << '\n';
        } 
    );
    return 0;
}

Nous copions donc toutes les données du début à la fin de la dataList vers le paramètre vecteur, si et seulement si, la deuxième chaîne du vecteur source (index = 1) est différente de notre ancienne valeur mémorisée.

Plutôt simple.

Un autre l'optimisation serait de trier immédiatement les paramètres corrects et de ne pas stocker le vecteur complet avec toutes les données en premier lieu, mais de ne stocker que les données nécessaires. Cela réduira considérablement la mémoire nécessaire.

Modifiez votre boucle while en:

string line = "";                              
string oldValue{};
while (getline(file, line))                 //Iterate through each line 
                                            //and split the content 
                                            //using delimiter
{
    vector<string> vec;                       //Vector contains a row from 
                                                //input file 
    boost::algorithm::split(vec, line, boost::is_any_of(delimiter));

    if (oldValue != vec[1]) {
        dataList.push_back(vec);
    }
    oldValue = vec[1];
}

Avec cela, vous y parvenez dès le début.

Une solution supplémentaire est comme ci-dessous

std::copy_if(dataList.begin(), dataList.end(), std::back_inserter(parameter),
    [lastID = std::string{}](const std::vector<std::string> & sv) mutable {
        bool result = (lastID != sv[1]);
        lastID = sv[1];
        return result;
    }
);

Remarque: dans la fonction main, nous faisons tout dans une seule instruction: std :: copy_if . La source est dans ce cas un std :: istream donc un std :: ifstream (un fichier) ou celui que vous voulez. Dans SO, j'utilise un std :: istringstream car je ne peux pas utiliser de fichiers ici. Mais c'est pareil. Remplacez simplement la variable dans le std :: istream_iterator . Nous parcourons le fichier avec le std::istream_iterator.

Quel dommage que personne ne lise ceci. . .


2 commentaires

Merci pour votre réponse. Cela semble très logique, mais en l'essayant moi-même, je n'ai pas pu l'exécuter avec mon fichier d'exemple (votre liste de chaînes a fonctionné). Quoi qu'il en soit ... Je suis assez débutant et j'ai donc été perdu en lisant votre style de codage. Si vous pouviez expliquer un peu plus en détail votre code ou me dire pourquoi vous utilisez istream_iterator <> deux fois par exemple (ou la raison de la surcharge de l'extracteur), je vous en serais très reconnaissant!


Edit: a exécuté avec succès la boucle modifiée avec votre if () - fonction! Maintenant, je dois ajouter quelques conditions supplémentaires. Merci beaucoup @Armin!



0
votes

Ok les gars, je jouais avec mon code et je me suis rendu compte que la deuxième solution @Armins (boucle while modifiée) ne considère pas les listes non ordonnées, c'est-à-dire que si un élément réapparaît beaucoup plus tard, il est comparé à l'élément précédent (oldValue ) et inséré, bien qu'il existe déjà dans mon conteneur ...

Après quelques lectures (et plus doit venir évidemment), j'ai tendance à unordered_set de @ Paul. Ma première question se pose ici: pourquoi n'avez-vous pas suggéré à la place set ? D'après ce que j'ai trouvé, unordered_set est apparemment plus rapide pour les opérations de recherche. Dans mon esprit personnel très limité, c'est difficile à comprendre ... mais je ne veux pas creuser trop profondément ici. Est-ce votre raison? Ou y a-t-il d'autres avantages que j'ai manqués?

Malgré votre suggestion, j'ai essayé d'utiliser set , ce qui me semble mieux dans ma situation, car plus ordonnée. Et encore une fois mon code résiste à s'exécuter:

error: no matching function for call to 'std::set<std::vector<std::__cxx11::basic_string<char> > >::count(std::__cxx11::string&)'
     if(!localDetails.count(localDetail))

L'erreur dit:

set<vector<string> > CSVReader::getData() {

ifstream file(fileName);

set<vector<string> > container;

string line = "";
string uniqueValue{};

while (getline(file, line))                          //Iterate through each line and split the content using delimiter
{
    //Vector contains a row from RAO file
    vector<string> vec;                        
    boost::algorithm::split(vec, line, boost::is_any_of(delimiter));

    uniqueValue = vec[2];

    //Line (or vector) is added to container if the uniqueValue, e.g. 1_380, appears for the first time                   

    if(!container.count(uniqueValue))
    {
        container.insert(vec);
    }

}

file.close();
return container;  
}

Depuis que j'ai suivi votre exemple, qu'est-ce que Je fais une erreur?

PS: Je viens de lire sur les politiques SO ... j'espère que cette question supplémentaire sera acceptable


1 commentaires

Je suppose que j'ai trouvé mon problème. Puisque container contient des vecteurs de et que je recherche une string dans mon ensemble, cela ne fonctionnera pas ... Quiconque a une idée à résoudre ce?