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:
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?
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.
Je ferais ceci comme ceci:
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.
Si vous voulez utiliser plusieurs std::istreambuf_iterator
s, 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
Où 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
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.
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(¤tChar, 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;
}
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:
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
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