首页 > 解决方案 > 从输入流中读取行并能够跳过块

问题描述

我有一个带有一系列类似字节码的指令的输入流

function foo
push x
pop y
...
return
function bar
...
return
function other
...

即一系列连续的函数声明。每个功能都是从一个“功能”到下一个“功能”定义的。一个函数中可能有多个“返回”,因此我不能将其用作分隔符。所有指令都必须在函数内部(即流的第一行始终是“函数”,最后一行始终是“返回”)。

我想基本上从列表中删除某些功能。我有一个我想要保留的函数列表,我考虑复制到输出流,跳过不在列表中的任何函数,比如

vector<string> wanted_functions = { "foo", "other" }
ostringstream oss;
bool skip = false;
for (string line; getline(input_stream, line);) {
    istringstream iss(line);
    string command;
    iss >> command;
    if (command == "function") {
        skip = false;
        string function_name;
        iss >> function_name;
        if (std::find(wanted_function.begin(), wanted_functions.end(), function_name) 
                 == wanted_functions.end()) {
            skip = true;
        }
    if (!skip) oss << line;
}

我还没有测试过上述解决方案;看起来它可以工作,但我认为它不是很优雅。我觉得流迭代器在这里会很好,但我不知道如何使用它们。如何使用迭代器或本地流方法(如 ignore() 或 seekg())实现跳过行为?

奖励:如果有更好的方法来阅读行中的前两个单词,即为他们创建一个新的流,我也想知道。

编辑:函数总是顺序的。没有嵌套函数。即“函数”总是紧跟在“返回”之前。

标签: c++stream

解决方案


如果它是文本,你不能轻易地跳过/跳过(seekg)而不实际阅读它,因为你没有已知的偏移量(许多二进制文件格式将包含此类信息),但你可以过滤你所做的阅读,您问题中的代码几乎可以做到这一点。

istream_iterator<std:string>会给你每个单词/空格分隔,但你不知道新行在哪里。您可以创建一个istream_iterator将读取行的方法,但最简单的方法涉及std::string重新定义子类operator >>,但这基本上是getline您得到的,或者您可以创建自己的类型,其中包含更多有用的信息(如下)。


您可以使用std::unordered_set<std::string> wanted_functions它来检查项目是否存在,而不是搜索std::vector(有std::find或类似)。skip当您将其设置为“不需要的”功能时,最终也会稍微奇怪地工作,然后执行 like if (!unwanted).

unordered_set<string> wanted_functions = { "foo", "other" };
bool is_wanted_function = false;
for (string line; getline(input_stream, line);) {
    istringstream iss(line);
    string command;
    iss >> command;
    if (command == "function") {
        string function_name;
        iss >> function_name;
        is_wanted_function = wanted_functions.count(function_name) != 0;
    }
    if (is_wanted_function) {
        oss << line << std::endl;
    }
}

is_wanted_function标志的替代方法是使用 中的函数if (command == "function") {,这需要更仔细地管理读取下一行,以免意外跳过内部循环后面的那个

unordered_set<string> wanted_functions = { "foo", "other" };
string line;
getline(input_stream, line);
while (input_stream) {
    istringstream iss(line);
    string command;
    iss >> command;
    if (command == "function") {
        string function_name;
        iss >> function_name;
        if (wanted_functions.count(function_name)) {
            oss << line << std::endl;
            while (getline(input_stream, line) && line.rfind("function", 0) != 0) {
                oss << line << std::endl;
            }
            continue; // already have a line
        }
    }
    getline(input_stream, line); // next line
}

因为我不认为这是一个很大的改进,但如果实际的解析(iss >> command;,iss >> function_name等)在其他地方被重构,那么它会更简单一些。


您可能会进行实际的解析(获取像“function”这样的命令名称,以及像“foo”这样的参数)它是自己的类,可以整理istringstream iss(line); iss >> command;直接在此代码中的等。

istream_iterator基本上只是用于operator >>获取下一项,直到流处于失败状态,因此可以与您自己的类型一起使用,尽管您可以在没有istream_iterator.

class command
{
public:
    const std::string &cmd()const { return _cmd; }
    const std::string &source_line()const { return _source_line; }
    const std::string &arg(size_t i)const
    {
        if (i < _args.size()) return _args[i];
        else throw std::out_of_range("Command does not have this many arguments.");
    }

    friend std::istream &operator >> (std::istream &is, command &cmd)
    {
        if (std::getline(is, cmd._source_line))
        {
            std::stringstream ss(cmd._source_line);
            ss >> cmd._cmd;
            cmd._args.clear(); // istream_iterator uses the same command object every time
            while (true)
            {
                std::string val;
                ss >> val;
                if (!ss) break;
                cmd._args.push_back(std::move(val));
            }
        }
        return is;
    }
private:
    std::string _source_line;
    std::string _cmd;
    std::vector<std::string> _args;
};
int main()
{
    using namespace std;
    std::stringstream input_stream(
        "function foo\n"
        "push x\n"
        "pop y\n"
        "...\n"
        "return\n"
        "function bar\n"
        "...\n"
        "return\n"
        "function other\n"
        "...\n"
        "return\n");
    std::ostream &oss = std::cout;

    std::unordered_set<string> wanted_functions = { "foo", "other" };
    std::istream_iterator<command> eos; // end of stream
    std::istream_iterator<command> it(input_stream); // iterator
    while (it != eos)
    {
        if (it->cmd() == "function" && wanted_functions.count(it->arg(0)))
        {
            do
            {
                oss << it->source_line() << std::endl;
            } while (++it != eos && it->cmd() != "function");
        }
        else ++it; // on true the while loop already advanced
    }
}

istream_iterator当然也带来了与其他基于迭代器的算法和构造函数(std::find等)的兼容性,并且您可以从中构建一些更复杂的东西。例如,如果您在此之上添加另一层来创建一个istream_iterator<function>,那么也许您可以使用 Boost C++ filter_iterator,然后您将拥有一个仅包含您想要的函数的迭代器。


请注意,如果您需要开始处理任何嵌套结构(如if (...) { ... } else if (...) { ... }),您可能会发现解析为树结构比平面序列更方便进行操作。请参阅抽象语法树。这在某种程度上取决于您的语法,例如,如果您只使用goto if偏移量/标签而不是while(expr), if(expr), else if,else等类型构造。


推荐阅读