我的程序的目的是打开一个长度为 n 的 m 行的文本文件,逐列读取文件并打印每一列。
例如,对于此文本文件
abcd
efgh
jklm
我想打印
a e j
b f k
c g l
d h m
由于一行的长度可以是2000000000,而列的长度可以超过10000,所以我无法以矩阵形式打开内存中的所有文件。
从理论上讲,我希望有一个程序在空间上使用O(m),在时间上使用O(m * n)。
一开始,我必须考虑以下解决方案:
最后一点,对于某些服务器问题,我只需要使用STL。
我的最后一个想法是创建一个文件迭代器数组,并在每行的开头初始化这些迭代器。之后,要看下一篇专栏文章,我只需要增加每个迭代器。这是我的代码
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;
}
可悲的是,它不起作用,我将其作为输出
a e f
� � �
� � �
� � �
我认为问题在于迭代器 iarray 的数组不独立于 istart ,我该怎么做?
答案 0 :(得分:6)
您可以将任务分解为多个块,然后在继续进行下一个之前处理每个块。
每行需要一个缓冲区(该行越大,性能越好)和该行的查找位置。您可能还需要通过文件进行初始传递,以获取每一行的正确偏移量。
将B字节读入缓冲区中的每一行(使用"="
保存每一行中的位置),然后遍历这些字节并生成输出。返回并从每一行读取下一个B字节(使用tellg
来预先设置文件位置,然后使用seekg
来记住它)并生成输出。重复执行直到完成为止,注意最后一个块(或输入较小的字符),以免超出行尾。
使用您的示例,您需要跟踪3行。使用2的B大小,您将tellg
,ab
和ef
读入3个缓冲区中。循环输出您要输出的jk
和aej
。返回并阅读下一个块:bfk
,cd
和gh
。这样会得到lm
和cgl
作为输出。
答案 1 :(得分:5)
我会这样:
IMO,您做得更好。最关键的是如何选择正方形的大小。建议使用2的大幂。
答案 2 :(得分:1)
如果您想使用多个std::istreambuf_iterator
来执行此操作,那么您将需要多个fstreams
来执行操作,否则,当您迭代一个(例如istart++
)时会影响< em> all 该fstream
的迭代器,这意味着下次您迭代一个(即*iarray[i]++
)时,您将跳过一个字符。 reference中对此进行了更清晰的解释。请考虑以下代码段:
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;
将输出
i1 - a i2 - a
i1 - b i2 - a
i1 - b i2 - c
在流中i2
似乎已“跳过” b
的地方。即使您稍后分配了第二个迭代器,也就是
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;
输出保持不变-
i1 - a i2 - a
i1 - b i2 - a
i1 - b i2 - c
由于两种情况下,两个迭代器都作用于相同的流对象,因此,每次迭代一个迭代器时,都会从流中删除一个字符。在所讨论的代码中,每个迭代器(istart
,iarray[i]
)都作用于相同的流对象,因此其中每个迭代器的每次迭代都会从流中删除char
。然后,输出很快就是未定义行为的结果,因为未定义超出流结束的迭代(并且由于迭代器正在一起迭代,因此您可以很快到达它)。
如果要按照轮廓的方式进行操作,则只需要多个fstream
对象,例如
#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;
}
}
哪个会产生您期望的输出,
a e j
b f k
c g l
d h m
相对于所建议的其他方法,我无法对这种方法的速度做出任何评论,但这就是您使用该方法所要执行的操作。
答案 3 :(得分:0)
一般注意事项
如果迭代器数组有效,则必须遍历内存(另请参见威廉·米勒),或者应在哪里遍历?
权衡是:
权衡解决方案4
需要更多有关边界条件的知识。
解决方案4的概念取决于许多未知条件
对原始程序的问题分析
问题还在于:为什么它不起作用。
程序...
#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;
}
...读取test.data包含...
abcdefghjklm
...然后程序打印出来...
e
e
因此,循环...
while (istart != iend){
if (x % nbcolumn == 0){
iarray[x/nbcolumn] = istart;
}
istart++;
x++;
}
...不会导致预期的结果,因为迭代器的工作方式不同,每次调用...
iarray[i]++;
...正在同时操纵所有迭代器。
权衡解决方案3
出路是什么?根据权衡#3创建代码。
程序...
#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;
}
...创建输出:
a e j
b f k
c g l
d h m
答案 4 :(得分:0)
您不能使用istreambuf_iterator两次,只能使用一次。无论如何,下面的希望代码对您有帮助
让我解释一下我首先要做什么; 您知道顺序进行文件读取会快得多。我在那里所做的是缓冲读取。可以说,在您的示例中,我正在缓冲两行,因此我必须分配6个字节的缓冲区并用搜索填充它;每次读取将读取两个字节,因为我们持有两行。这可以优化,尽管如果您在立即阅读时打印出第一个字符,则在示例中仅使用3个字节就可以缓冲两行,而在示例中可以仅通过缓冲6个字节来缓冲三行。无论如何,我会给你它的非优化版本。
再次提醒您,您不能两次使用istreambuf_iterator:How do I use an iterator on an ifstream twice in C++?
如果必须使用迭代器,则可以实现可以在文件上查找和读取的迭代器;可能真的很乱,
#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;
}