【问题标题】:How can I parse a bracketed string to a list of string with a given delimitor如何将括号中的字符串解析为具有给定分隔符的字符串列表
【发布时间】:2020-11-02 16:03:03
【问题描述】:

我的任务是解析带括号的字符串,比如

[foo | bar | foobar],到vectorstd::strings

在这种情况下, vector 应该以内容 {"foo" , "bar", "foobar"} 结尾。

这些括号可以嵌套。例如,给定的括号字符串

[[john | doe] | [ bob | dylan]]

会变成{ "[john | doe]" , "[bob | dylan] }"

到目前为止我能做到的最好的事情是

int main(int argc, char ** argv)
{
    const std::string input {argv[1]};
    std::vector<std::string> res;

    qi::phrase_parse(input.cbegin(), input.cend(),
     '[' 
     >> *qi::lexeme[ +(qi::char_ - '|')  >> '|'] 
     > -qi::lexeme[ +(qi::char_  - ']') >> ']' ],
     qi::space ,
     res);

    for (const auto& v: res)
        std::cout << v  <<std::endl;

    return 0;
}

这对于嵌套的情况非常失败。有人可以指出我正确的方向吗?

注意 #1:嵌套案例可以不止一个。

注意 #2:我欢迎任何更简单的解决方案,即使不使用 Boost Spirit。

【问题讨论】:

  • 也许使用堆栈数据结构来跟踪您遇到了多少[ 大括号?每次遇到],你就将push_back从最后一个[到最后一个]的子串放入最后一个vector然后出栈。
  • 实际上,您能详细解释一下|[ 符号的工作原理吗?
  • 您的示例正在去除与| 相邻的空格。这是必需的吗?不带空格的| 是有效的分隔符吗?

标签: c++ boost-spirit boost-spirit-qi


【解决方案1】:

这是一个简单的 C++ 解析器,它假设括号是平衡的,即每个 [ 都有一个 ]

bracket 是左括号的数量。当该号码为 1 时,我们会做出重要决定。

#include <iostream>
#include <vector>
#include <string>
#include <string_view>

bool edge(const int num){
    return num == 1;
}

int main(){
    std::vector<std::string> all;
    std::string line;
    // std::getline(std::cin, line);
    line = "[[john | doe] | [ bob | dylan]]";

    int bracket = 0;
    std::string::size_type start = 0;
    for(int i = 0; i < line.size(); i++){
        const char c = line[i];
        if(c == '['){
            bracket++;
            if(edge(bracket)){
                start = i + 1;
            }
        }
        if(c == ']'){
            if(edge(bracket)){
                all.push_back(line.substr(start, i - start));
            }
            bracket--;
        }
        if(c == '|' && edge(bracket)){
            all.push_back(line.substr(start, i - start));
            start = i + 1;
        }
    }
    for(std::string_view t : all){
        std::cout << t << std::endl;
    }
}

【讨论】:

  • “当这个数字为 1 时,我们会做出重要的决定。”如果您总是测试括号内的数字,那将起作用。您可以检查括号外的数字是否为零。 (不是真的,因为你也有|,它纯粹是在里面)不幸的是,你的逻辑目前正在测试 before 括号,它是半内半外并且不起作用。您希望代码等效于 if(edge(++bracket))if(edge(bracket--))
  • 我确实觉得有必要创建一个函数来更好地表明我们在包含当前c 后只有一个括号,或者在包含当前c 后只有一个括号的情况。 //“并且不工作”但是我在发布之前已经测试过..让我再试一次..这可能是最后一分钟的匆忙更改。
  • 是的,因为当正确的行为是匹配(并排除)最外面的括号时,您实际上匹配(并包括)[[john | doe] | [ bob | dylan]] 输入的 second 括号.
【解决方案2】:

如果你想要嵌套的字符串列表,首先你需要一个可以存储嵌套列表的结果。幸运的是,在 C++17 中,您可以拥有前向引用的向量(只要它们在某个时候定义)。所以你可以创建一个列表类型,其中每个项目要么是字符串,要么是另一个列表:

struct Expr : std::vector<
    boost::variant<
        std::string,
        Expr>>
{ 
    using std::vector<boost::variant<std::string, Expr>>::vector;
};

之后的语法就很简单了。请注意,它是递归的 - Term 可以有一个 Expr 嵌套在其中:

WORD = /[^\[\|\]]+/
Term = WORD | Expr
Expr = '[' Term ('|' Term)* ']';

您可以分别表达每条规则。 Boost Spirit Qi 方便地使用% 运算符,它解析分隔列表并将其插入到容器中。

using It = std::string::const_iterator;
using Sk = qi::space_type;
qi::rule<It, std::string(), Sk> word;
qi::rule<It, boost::variant<std::string, Expr>(), Sk> term;
qi::rule<It, Expr(), Sk> expr;

word = +(qi::char_ - '[' - '|' - ']'); 
term = word | expr;
expr = '[' >> (term % '|') >> ']';

然后qi::phrase_parse 会做你想做的事:

Expr res;
qi::phrase_parse(input.cbegin(), input.cend(), expr, qi::space, res);

演示:https://godbolt.org/z/5W993s

【讨论】:

  • 此代码使用 gcc 8.3.1 和 -std=c++11 编译。这里实际上需要 C++17 什么?
  • C++17 需要在标准中保证您可以描述带有前向引用的std::vector。 gcc 的 libstdc++ 恰好以这种方式为 C++11 实现它,如果它满足您的要求就可以了。如果没有,您可以使用 unique_ptr 的向量作为自身,但您必须使用 Spirit X3,因为 Qi 无法感知移动。
  • 如何更改示例以返回获取 std::vector<:string> ?
  • 那个向量会是什么?只是第一级,还是扁平化的单词列表?
  • 只是第一级。想法是然后在每个元素上再次调用解析函数(因为分隔符可能会在每个级别之间更改,例如,“[ [ aa;bb] | [cc;dd] ]” => 会期望得到一个向量 { "[aa;bb]" , "[cc;dd]"} 并递归地对每个元素应用解析函数,将分隔符从 | 更改为 ;
【解决方案3】:

这个更简单的版本似乎正是你想要的:

qi::phrase_parse(input.cbegin(), input.cend(),
    '[' 
    >> qi::lexeme[ +~qi::char_("|]") ] % '|' 
    >> ']',
    qi::space,
    res);

它会解析:

"foo "
"bar "
"foobar"

也许您实际上并不希望将空格作为匹配的一部分。那么它可以更简单:

qi::phrase_parse(input.cbegin(), input.cend(),
    '[' 
    >> qi::lexeme[ +(qi::graph - qi::char_("|]")) ] % '|' 
    >> ']',
    qi::space,
    res);

Live On Coliru

注意:如果您有 C++14,请考虑使用 X3:Live On Coliru。这样编译起来会快很多

【讨论】:

  • 感谢您的回答。似乎此解决方案不适用于嵌套案例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
  • 2023-03-11
  • 1970-01-01
  • 2020-09-20
  • 2016-04-17
  • 2011-03-18
相关资源
最近更新 更多