web-dev-qa-db-fra.com

Itérateurs multi flux c ++

Le but de mon programme est d’ouvrir un fichier texte de m lignes de même longueur n , lire le fichier colonne par colonne et imprimer chaque colonne. 

Par exemple, pour ce fichier texte 

abcd
efgh 
jklm

Je voudrais imprimer

a e j
b f k
c g l
d h m

Comme une longueur de ligne peut être 200 000 000 et une longueur de colonne peut être supérieure à 10 000, je ne peux pas ouvrir tout le fichier en mémoire dans une matrice.

Théoriquement, j'aimerais un programme qui utilise O(m) dans l'espace et O (m * n) dans le temps.

Au début, je devais réfléchir à ces solutions:

  • si je vois tout le fichier pour chaque colonne, la complexité est O (m * n²),
  • Si j'utilise seekg et un tableau de positions et que je saute de position en position, la complexité est de O (m n log (n)).

Dernier point, pour certains problèmes de serveur, je dois utiliser uniquement la STL.

Ma dernière idée est de créer un tableau d'itérateurs d'un fichier et d'initialiser ces itérateurs au début de chaque ligne. Après cela, pour voir la colonne suivante, il suffit d’augmenter chaque itérateur. C'est mon code

ifstream str2;
str2.open ("Input/test.data", ifstream::in);

int nbline = 3;
int nbcolumn = 4;
int x = 0;

istreambuf_iterator<char> istart (str2);
istreambuf_iterator<char> iend ;

istreambuf_iterator<char>* iarray;
iarray = new istreambuf_iterator<char>[nbline];


while (istart != iend){
    if (x % nbcolumn == 0){
        iarray[x/nbcolumn] = istart;
    }
    istart++;
    x++;
}

for (int j = 0; j<nbcolumn;j++){
    for (int i = 0; i<nbline;i++){
        cout  << *iarray[i] << "\t";
        iarray[i]++;
    }
    cout << endl;
}

Malheureusement, ça ne marche pas et j'ai cette chose en sortie

a       e       f       
�       �       �       
�       �       �       
�       �       �       

Je pense que le problème est que le tableau d'itérateurs iarray n'est pas indépendant de istart , comment puis-je faire cela?

14
B. Hel

Vous pouvez diviser la tâche en morceaux, puis traiter chaque morceau avant de passer au suivant.

Vous auriez besoin d'un tampon pour chaque ligne (plus ce sera grand, meilleure sera la performance) et de la position de recherche pour cette ligne. Vous devrez peut-être également effectuer une première passe dans le fichier pour obtenir les décalages corrects pour chaque ligne.

Lisez B octets dans le tampon pour chaque ligne (en utilisant tellg pour enregistrer la position dans chaque ligne), puis passez en boucle sur ceux-ci et générez votre sortie. Revenez en arrière et lisez les B octets suivants de chaque ligne (en utilisant seekg pour définir la position du fichier au préalable, et tellg pour le mémoriser par la suite) et générez la sortie. Répétez l'opération jusqu'à ce que vous ayez terminé, en faisant attention au dernier morceau (ou aux petites entrées) pour ne pas aller au-delà de la fin de la ligne.

En utilisant votre exemple, vous avez 3 lignes à suivre. En utilisant une taille B de 2, vous liriez dans ab, ef et jk dans vos 3 tampons. Boucle sur ceux que vous auriez générés aej et bfk. Revenez en arrière et lisez les morceaux suivants: cd, gh et lm. Cela donne cgl et dhm en sortie.

6
1201ProgramAlarm

Je ferais ceci comme ceci:

  1. Ouvrez le fichier source.
  2. Mesurer la taille de la ligne
  3. Mesurer le nombre de lignes (taille du fichier/(taille de la ligne + taille de la fin de vie)). Remarque EOL peut être 2 octets.
  4. calculer la taille du fichier de résultat. Ouvrez le fichier de résultats et forcez-le à la taille souhaitée. Vous pourrez ainsi rechercher ultérieurement n'importe quelle partie du fichier.
  5. pic d'une certaine taille de carré qui est mémoire gérable. Par exemple, 1024x1024
  6. Maintenant, vous devriez charger une partie carrée de la matrice. 1024 éléments pour les rangées de 1024 rangées constitutives.
  7. Carré de transposition 
  8. Ecrivez-le dans le fichier de destination en cherchant dans la colonne appropriée de chaque partie de la ligne que vous écrivez. (vous pouvez réduire la consommation de mémoire au point précédent en transposant une colonne, puis en l'écrivant sous forme de ligne, au lieu de transposer le carré entier en une fois)
  9. Itérer carré sur toute la matrice du fichier 

OMI, vous ne pouvez pas faire mieux. Le plus critique sera de savoir comment choisir la taille du carré. Une grande puissance de 2 est recommandée.

5
Marek R

Si vous voulez utiliser plusieurs std::istreambuf_iterators, vous aurez besoin de plusieurs fstreams, sinon, lorsque vous itérerez un (ie istart++) qui affectera tous les itérateurs de cette fstream, itérer un (ie *iarray[i]++), vous passerez un caractère. Ceci est expliqué plus clairement dans la référence référence . Considérez cet extrait:

std::ifstream str;
str.open("test.data", std::ifstream::in);

std::istreambuf_iterator<char> i1 (str);
std::istreambuf_iterator<char> i2 (str);

std::cout << "i1 - " << *i1 << "   i2 - " << *i2 << std::endl;
i1++;
std::cout << "i1 - " << *i1 << "   i2 - " << *i2 << std::endl;
i2++;
std::cout << "i1 - " << *i1 << "   i2 - " << *i2 << std::endl;

qui va sortir 

i1 - a   i2 - a
i1 - b   i2 - a
i1 - b   i2 - c

i2 est apparu pour "ignorer" b dans le flux. Même si vous affectez le deuxième itérateur plus tard, c’est-à-dire.

std::ifstream str;
str.open("test.data", std::ifstream::in);

std::istreambuf_iterator<char> i1 (str);
std::istreambuf_iterator<char> i2;
std::istreambuf_iterator<char> iend;

int x = 0;
while (i1 != iend) {
    if (x % 4 == 0) {
        i2 = i1;
        break;
    }
    x++;
    i1++;
}

std::cout << *i1 << " " << *i2 << std::endl;
i1++;
std::cout << *i1 << " " << *i2 << std::endl;
i2++;
std::cout << *i1 << " " << *i2 << std::endl;

la sortie reste la même - 

i1 - a   i2 - a
i1 - b   i2 - a
i1 - b   i2 - c

Pourquoi? 

Dans les deux cas, les deux itérateurs agissent sur le même objet de flux et chaque fois que vous en répétez un, il supprime un caractère du flux. Dans le code en question, chaque itérateur (istart, iarray[i]) agit sur le même objet de flux et, par conséquent, chaque itération de l'un d'eux supprime un char du flux. La sortie est alors rapidement le résultat d'un comportement indéfini, car itérer au-delà de end-of-stream n'est pas défini (et puisque les itérateurs itèrent ensemble, vous l'atteignez rapidement).


Si vous souhaitez procéder de la manière dont vous avez défini le contour, vous avez simplement besoin de plusieurs objets fstream, tels que 

#include <fstream>
#include <string>
#include <iostream>


int main(int argn, char** argv) {
    std::ifstream str2;
    str2.open ("test.data", std::ifstream::in);

    int nbline = 3;
    int nbcolumn = 4;
    int x = 0;

    std::istreambuf_iterator<char> istart (str2);
    std::istreambuf_iterator<char> iend ;

    std::ifstream* streams = new std::ifstream[nbline];
    for (int ii = 0; ii < nbline; ii++) {
        streams[ii].open("test.data", std::ifstream::in);
    }
    std::istreambuf_iterator<char>* iarray = new std::istreambuf_iterator<char>[nbline];
    for (int ii = 0; ii < nbline; ii ++) {
        iarray[ii] = std::istreambuf_iterator<char> (streams[ii]);
    }

    int idx = 0;
    while (istart != iend) {
        if (x % nbcolumn == 0) {
            std::advance(iarray[x/nbcolumn], (nbcolumn+1)*idx);
            idx++;
        }
        x++;
        istart++;
    }

    for (int ii = 0; ii < nbcolumn; ii ++) {
        for (int jj = 0; jj < nbline; jj ++) {
            std::cout << *iarray[jj]++ << "\t";
        }
        std::cout << std::endl;
    }
}

Qui produit la sortie que vous attendez,

a       e       j
b       f       k
c       g       l
d       h       m

Je ne peux faire aucun commentaire sur la vitesse de cette méthode par rapport aux autres suggestions, mais voici comment vous feriez ce que vous demandez en utilisant cette méthode.

1
William Miller

Vous ne pouvez pas utiliser istreambuf_iterator deux fois, il ne peut être utilisé qu'une seule fois. Quoi qu'il en soit, le code ci-dessous vous aide

Permettez-moi d’expliquer ce que j’essaie de faire en premier; Vous savez que les lectures de fichiers sont beaucoup plus rapides lorsque vous le faites de manière séquentielle. Ce que je fais là est lu en tampon. Disons que, dans votre exemple, je mets en mémoire tampon deux lignes, je dois donc allouer 6 octets de mémoire tampon et la remplir de recherches; Chaque lecture lira deux octets car nous avons deux lignes. Ceci peut être optimisé si vous imprimez le premier caractère en lisant immédiatement, vous pouvez mettre deux lignes en mémoire tampon en utilisant simplement 3 octets et trois lignes en mettant en mémoire tampon 6 octets dans votre exemple. Quoi qu'il en soit, je vous en donne une version non optimisée.

Encore une fois, permettez-moi de vous rappeler que vous ne pouvez pas utiliser istreambuf_iterator deux fois: Comment utiliser un itérateur sur un ifstream deux fois en C++?

si vous devez utiliser un itérateur, vous pouvez implémenter votre itérateur qui peut rechercher et lire sur un fichier; peut être vraiment désordonné si ,

#include <iostream>
#include <fstream>
#include <vector>
#include <stdexcept>
#include <sstream>
#include <algorithm>

std::vector<std::size_t> getPositions(std::ifstream& str2, int &numcolumns) {
    std::vector<std::size_t> iarray;

    iarray.Push_back(0); // Add first iterator

    bool newlinereached = false;
    int tmpcol = 0;
    int currentLine = 0;
    char currentChar = 0;
    char previosChar = 0;

    numcolumns = -1;

    for (str2.seekg(0, std::ios_base::beg); !str2.eof(); previosChar = currentChar) {
        const std::size_t currentPosition = str2.tellg();
        str2.read(&currentChar, 1);
        if (newlinereached) {
            if (currentChar == '\r') {
                // Always error but skip for now :)
                continue;
            }
            else if (currentChar == '\n') {
                // ERROR CONDITION WHEN if (numcolumns < 0) or previosChar == '\n'
                continue;
            }
            else if (tmpcol == 0) {
                throw std::runtime_error((std::stringstream() << "Line " << currentLine << " is empty").str());
            }
            else {
                if (numcolumns < 0) {
                    // We just found first column size
                    numcolumns = tmpcol;
                    iarray.reserve(numcolumns);
                }
                else if (tmpcol != numcolumns) {
                    throw std::runtime_error((std::stringstream() << "Line " << currentLine
                        << " have incosistend number of columns it should have been " << numcolumns).str());
                }

                iarray.Push_back(currentPosition);
                tmpcol = 1;
                newlinereached = false;
            }
        }
        else if (currentChar == '\r' || currentChar == '\n') {
            newlinereached = true;
            ++currentLine;
        }
        else {
            tmpcol++;
        }
    }

    if (currentChar == 0) {
        throw std::runtime_error((std::stringstream() << "Line " << currentLine
            << " contains 'null' character " << numcolumns).str());
    }

    str2.clear(); // Restart 

    return iarray;
}

int main() {
    using namespace std;

    ifstream str2;
    str2.open("Text.txt", ifstream::in);
    if (!str2.is_open()) {
        cerr << "Failed to open the file" << endl;
        return 1;
    }

    int numinputcolumns = -1;

    std::vector<std::size_t> iarray =
        getPositions(str2, numinputcolumns); // S(N)

    const std::size_t numinputrows = iarray.size();

    std::vector<char> buffer;
    const int numlinestobuffer = std::min(2, numinputcolumns); // 1 For no buffer

    buffer.resize(numinputrows * numlinestobuffer); // S(N)

    const std::size_t bufferReadMax = buffer.size();


    for (int j = 0; j < numinputcolumns; j += numlinestobuffer)
    {
        // Seek fill buffer. Needed because sequental reads are much faster even on SSD
        // Still can be optimized more: We can buffer n+1 rows as we can discard current row read
        std::size_t nread = std::min(numlinestobuffer, numinputcolumns - j);
        for (int i = 0; i < numinputrows; ++i)
        {
            str2.seekg(iarray[i], ios_base::beg);
            size_t p = str2.tellg();
            str2.read(&buffer[i * numlinestobuffer], nread);
            iarray[i] += nread;
        }

        // Print the buffer
        for (int b = 0; b < nread; ++b)
        {
            for (int k = 0; k < numinputrows; ++k) {
                std::cout << buffer[b + k * numlinestobuffer] << '\t';
            }
            std::cout << std::endl;
        }
    }

    return 0;
}
0
Abdurrahim

Considérations générales

Si le nombre d'itérateurs fonctionnait, il devrait parcourir la mémoire (voir aussi la réponse de William Miller), ou où devrait-il se parcourir?

Le compromis est:

  1. Analyse jusqu'à la fin de la première ligne de sortie, identique pour toutes les autres lignes de sortie
    • lent, presque pas de mémoire utilisée
  2. Remplir une matrice complètement et sortir la matrice transposée
    • beaucoup de mémoire à utiliser
  3. Créez un tableau de positions pour toutes les lignes de sortie, recherchez dans toutes les positions
    • utilisation rapide et raisonnable de la mémoire
  4. Un mélange très intelligent de méthodes 2 et 3 .
    • prendre le temps possible le plus court avec une mémoire donnée (par exemple, disons 8 Go de RAM).

Solution de compromis 4

Besoin de plus de connaissances concernant la condition aux limites.

Un concept pour la solution 4 dépend de nombreuses conditions inconnues

  • Quelles sont les caractéristiques des données d'entrée?
    • Est-ce que 200TByte pour une matrice ou pour plusieurs matrices?
    • Pour combien?
    • Quel est le pire cas de ratio entre colonnes et lignes?
    • Est-ce juste des caractères simples ou peut être des mots?
    • S'il ne s'agit que de caractères simples, est-il assuré que chaque ligne a la même taille de mémoire?
    • Sinon, comment reconnaître une nouvelle ligne?
  • Combien de mémoire libre RAM est disponible?
  • À quelle vitesse l'ordinateur cible remplit-il toute la mémoire libre RAM?
  • Quel est le temps maximum acceptable?

Analyse du problème avec le programme d'origine

La question est aussi: pourquoi ça ne marche pas.

Le programme ...

#include    <fstream>
#include    <string>
#include    <iostream>

int main(int argc, char* argv[]) {
    std::ifstream str2;
    str2.open ("test.data", std::ifstream::in);

    std::istreambuf_iterator<char> istart(str2);
    std::istreambuf_iterator<char> iend;
    std::istreambuf_iterator<char> iarray1 = istart;

    istart++;
    istart++;
    istart++;
    istart++;
    std::istreambuf_iterator<char> iarray2 = istart;

    std::cout  << *(iarray1);
    std::cout << std::endl;
    std::cout  << *(iarray2);
    std::cout << std::endl;
    return 0;
}

... lit test.data contient ...

abcdefghjklm

... et le programme imprime ...

e
e

Par conséquent, la boucle ...

while (istart != iend){
    if (x % nbcolumn == 0){
        iarray[x/nbcolumn] = istart;
    }
    istart++;
    x++;
}

... ne mènera pas au résultat attendu, car l'itérateur fonctionne de manière différente, et chaque appel de ...

iarray[i]++;

... manipule tous les itérateurs en même temps.


Solution de compromis 3

Quel est le moyen de sortir? Création de code en fonction du compromis # 3.

Le programme...

#include    <iostream>
#include    <ios>
#include    <string>
#include    <fstream>

int main(int argc, char* argv[]) {
    int nbline = 3;
    int nbcolumn = 4;
    std::ifstream   fsIn;
    std::streampos  posLine[nbline];
    std::streampos  posTemp;

    fsIn.open("test.data", std::ifstream::in);
    for ( int i = 0; i < nbline; i++) {
        posLine[i] = posTemp;
        posTemp += nbcolumn;
    }

    for ( int j = 0; j < nbcolumn; j++) {
        for ( int i = 0; i < nbline; i++) {
            fsIn.seekg(posLine[i]);
            std::cout  << char(fsIn.get()) << " ";
            posLine[i] = fsIn.tellg();
        }
        std::cout << std::endl;
    }
    return 0;
}

... crée la sortie:

a e j
b f k
c g l
d h m
0