多流迭代器C ++

时间:2018-10-22 10:14:38

标签: c++ stream istream-iterator

我的程序的目的是打开一个长度为 n m 行的文本文件,逐列读取文件并打印每一列。

例如,对于此文本文件

abcd
efgh 
jklm

我想打印

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

由于一行的长度可以是2000000000,而列的长度可以超过10000,所以我无法以矩阵形式打开内存中的所有文件。

从理论上讲,我希望有一个程序在空间上使用O(m),在时间上使用O(m * n)。

一开始,我必须考虑以下解决方案:

  • 如果我看到每一列的所有文件,则复杂度为O(m *n²),
  • 如果我使用seekg和一组位置并在位置之间跳转,则复杂度为O(m n log(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 ,我该怎么做?

5 个答案:

答案 0 :(得分:6)

您可以将任务分解为多个块,然后在继续进行下一个之前处理每个块。

每行需要一个缓冲区(该行越大,性能越好)和该行的查找位置。您可能还需要通过文件进行初始传递,以获取每一行的正确偏移量。

将B字节读入缓冲区中的每一行(使用"="保存每一行中的位置),然后遍历这些字节并生成输出。返回并从每一行读取下一个B字节(使用tellg来预先设置文件位置,然后使用seekg来记住它)并生成输出。重复执行直到完成为止,注意最后一个块(或输入较小的字符),以免超出行尾。

使用您的示例,您需要跟踪3行。使用2的B大小,您将tellgabef读入3个缓冲区中。循环输出您要输出的jkaej。返回并阅读下一个块:bfkcdgh。这样会得到lmcgl作为输出。

答案 1 :(得分:5)

我会这样:

  1. 打开源文件。
  2. 测量行大小
  3. 测量行数(文件大小/(行大小+ EOL的大小))。注意EOL可以是2个字节。
  4. 计算结果文件的大小。打开结果文件并强制其具有所需的大小,以便以后可以查找文件的任何部分。
  5. 达到某个正方形的大小,这是可管理的。例如1024x1024
  6. 现在,您应该加载矩阵的平方部分。 1024个组成行中的1024个元素。
  7. 转置正方形
  8. 通过查找要写入的行各部分的适当列,将其写入目标文件。 (您可以通过转置一列然后将其写为一行,而不是一次转置整个正方形来减少前一点的内存消耗)
  9. 遍历整个文件矩阵的正方形

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

为什么?

由于两种情况下,两个迭代器都作用于相同的流对象,因此,每次迭代一个迭代器时,都会从流中删除一个字符。在所讨论的代码中,每个迭代器(istartiarray[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)


一般注意事项

如果迭代器数组有效,则必须遍历内存(另请参见威廉·米勒),或者应在哪里遍历?

权衡是:

  1. 解析到第一条输出线完成为止,其他所有输出线都相同
    • 速度慢,几乎没有使用内存
  2. 完全填充矩阵并输出转置矩阵
    • 要使用的大量内存
  3. 为所有输出线创建一个位置数组,遍历所有位置
    • 快速,合理的内存使用
  4. 方法2和3的巧妙结合。
    • 在给定的内存(例如,假设8 GB RAM)的情况下,尽可能缩短时间。

权衡解决方案4

需要更多有关边界条件的知识。

解决方案4的概念取决于许多未知条件

  • 输入数据的特征是什么?
    • 200TByte是几个矩阵中的一个矩阵吗?
    • 多少?
    • 列与行之间的比率最坏的情况是什么?
    • 是单个字符还是单词?
    • 如果只是单个字符,是否可以确保每行具有相同的内存大小?
    • 否则,如何识别新行?
  • 有多少可用RAM内存?
  • 目标计算机填充所有可用RAM内存的速度有多快?
  • 可以接受的最长时间是多少?

对原始程序的问题分析

问题还在于:为什么它不起作用。

程序...

#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(&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;
}