c ++:解析包含表达式“访问多维数组”的字符串

Posted

技术标签:

【中文标题】c ++:解析包含表达式“访问多维数组”的字符串【英文标题】:c++: parse a string that contains an expression "access to the multidimensinal array" 【发布时间】:2019-07-24 13:28:12 【问题描述】:

如何为给定的字符串(如foo[0][1][2][3])编写一个辅助方法,将其拆分为数组的名称和索引的集合(例如向量)?在上面的示例中,它应该分别产生foo0, 1, 2, 3

字符串的格式总是像name[index_0][index_1]....[index_n]。 事先不知道索引的数量 (n)。都应该是数字。为简单起见,字符串中不允许有空格。数组的名称 (name) 可以是任意的。如果字符串不符合指定格式,辅助函数会抛出异常。

性能在这里不是问题。我正在寻找最优雅/最简短的解决方案。

更新

嗯,正则表达式是在第一条评论中提出的。我是该领域的新手,并且经历了用 C++ 完成它的麻烦。请随意简化它。与此同时,@MartinYork 和@Frodyne 提出了两个基于非正则表达式的解决方案。乍一看,正则表达式在这里并没有带来什么迷人之处。在我看来,解决方案似乎并没有更短或更优雅。

#include <stdexcept>
#include <iostream>
#include <string>
#include <regex>
#include <tuple>

std::tuple<std::string, std::vector<int>> helper(std::string str) 
  // used to validate that the incoming string is in format
  // array[0][1][2]
  const std::regex rx_validate
      "([[:alnum:]]+)((?:\\[[[:digit:]]+\\])+)$";

  std::match_results<std::string::const_iterator> match_results;
  std::regex_search(str, match_results, rx_validate);

  // regex_search array[0][1][2] gives
  // match_results[0]: array[0][1][2]
  // match_results[1]: array
  // match_results[2]: [0][1][2]
  if (match_results.size() == 3) 
    std::vector<int> indices;

    // used to extract indices, it is guaranteed that
    // numbers are between brackets, no extra checks
    // needed
    const std::regex rx_index"[0-9]+";
    const std::string matchmatch_results[2];
    auto it = std::sregex_iterator(match.begin(), match.end(), rx_index);
    for (; it != std::sregex_iterator(); ++it)
      indices.push_back(std::stoi((*it).str()));

    return std::make_tuple(match_results[1], indices);
   else 
    throw std::invalid_argument("Invalid format (" + str + ")");
  


int main() 
  const std::string str"a[0][1][2][3][4][5]";
  const auto tuple = helper(str);

  std::cout << "Name: " << std::get<0>(tuple) << std::endl;
  for (int index: std::get<1>(tuple))
    std::cout << index << std::endl;

更新2

@rici 建议修改使用正则表达式的算法。它更短更简洁。

我对比较这些算法的性能非常感兴趣。

不会提倡数字 :-) 每个人都应该自己决定。

以下程序编译为g++ -std=c++11 -Ofast 并在i7-8550U 上运行给出:

Regex measurements...
min/max/avg 955/154859/1072.88
Stream measurements...
min/max/avg 722/41252/800.402
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
#include <regex>
#include <tuple>

#include <time.h>

inline uint64_t Timestamp() 
  timespec time_now;
  clock_gettime(CLOCK_REALTIME, &time_now);
  return static_cast<uint64_t>(time_now.tv_sec) * 1000000000ULL + time_now.tv_nsec;


std::tuple<std::string, std::vector<int>> helper_stream(std::string const& info)

    std::stringstream is(info);
    std::string         name;
    std::vector<int>    index;

    if (std::getline(is, name, '[')) 
        is.putback('[');
        name.erase(std::remove(std::begin(name), std::end(name), ' '), std::end(name));

        int   value;
        char  b1;
        char  b2;
        while(is >> b1 >> value >> b2 && b1 == '[' && b2 == ']') 
            index.push_back(value);
        
    
    return std::make_tuple(name, index);


std::tuple<std::string, std::vector<int>> helper_regex(std::string str) 
    static const std::regex strip_prefix"^[[:alpha:]][[:alnum:]]*";
    static const std::regex index"\\[([[:digit:]]+)\\]|.";
    std::match_results<std::string::const_iterator> match;
    if (std::regex_search(str, match, strip_prefix)) 
        auto e = match[0].second;
        std::vector<int> indices;
        for (auto it = std::sregex_iterator(e, str.end(), index), lim = std::sregex_iterator(); it != lim; ++it) 
            if ((*it)[1].matched)
                indices.push_back(std::stoi((*it)[1]));
            else throw std::invalid_argument("Invalid format");
        
        return std::make_tuple(std::string(str.cbegin(), e), indices);
    
    else
        throw std::invalid_argument("Invalid format (" + str + ")");


std::string make_str(int n) 
  std::string str"array";

  for (int i = 0; i < n; ++i) 
    str += "[";
    str += std::to_string(std::rand());
    str += "]";
  

  return str;


template <typename F>
void measurements(F f) 
  constexpr int kNumRounds = 1000000;
  constexpr int kLength = 3;

  std::vector<uint64_t> time_diffs(kNumRounds);

  for (int i = 0; i < kNumRounds; ++i) 
    const std::string strmake_str(kLength);

    const auto before = Timestamp();
    f(str);
    const auto after = Timestamp();

    time_diffs[i] = after - before;
  

  uint64_t minstd::numeric_limits<uint64_t>::max();
  uint64_t maxstd::numeric_limits<uint64_t>::min();
  uint64_t sum0;

  for (int i = 0; i < kNumRounds; ++i) 
    const auto time_diff = time_diffs[i];

    if (time_diff < min)
      min = time_diff;

    if (time_diff > max)
      max = time_diff;

    sum += time_diff;
  

  std::cout << "min/max/avg " << min << "/" << max << "/" << static_cast<double>(sum) / kNumRounds << std::endl;


int main() 
  std::cout << "Regex measurements..." << std::endl;
  measurements(helper_regex);

  std::cout << "Stream measurements..." << std::endl;
  measurements(helper_stream);

  return 0;

【问题讨论】:

使用正则表达式。 @VladfromMoscow: Now they have two problems!:-) 您能否更准确地定义字符串的格式。它会一直有foo吗?它总是有四个索引吗?索引总是整数吗?字符串的任何部分是否允许/需要空格?输出的类型和格式有哪些? @MartinYork,感谢您的评论。我会更新原帖。 @TruLa:我认为正则表达式解决方案可以更优雅或至少更短。例如,coliru.stacked-crooked.com/a/7c6aedb1ecec2e36。请注意,调整正则表达式解决方案很简单,例如,接受表达式中的空格。 【参考方案1】:

这是我主张退回到 C 解析函数的少数几次之一。虽然可以通过正则表达式来完成,但对于如此琐碎的事情来说,这似乎有点沉重。

我会使用 C 函数 sscanf()

std::tuple<std::string, std::vector<int>> ck1(std::string const& info)


    int                 functionStartSize = 0;
    int                 functionNameSize = 0;
    char                check = 'X';
    std::vector<int>   index;

    if (std::sscanf(info.data(), " %n%*[^\[]%n%c", &functionStartSize, &functionNameSize, &check) == 1 && check == '[') 

        // Format String: " %n%*[^\[]%n%c"
        // ' ':        Ignore all leading space.
        // %n:         Save number of characters of space we dropped.
        // %*[^\[]:    Lets split this up
        //             %*      scan but don't save to a variable.
        //             [..]    Only the letters we find inside the brackets.
        //             ^\]     Everything except ]
        // %n:         Save the number of characters we have used to here.
        // %c:         A character This should now be a '['
        // We have correctly found the beginning and end of the name.

        int size;
        int value;
        int offset = functionNameSize;
        while(std::sscanf(info.data() + offset, "[%d%c%n", &value, &check, &size) == 2 && check == ']') 
            // We have found another index
            index.push_back(value);
            offset += size;
        
    
    return std::make_tuple(info.substr(functionStartSize, (functionNameSize-functionStartSize), index);

当我第一次编写上述代码时,我假设%n 会像任何其他参数一样计算。不幸的是,它不计入返回值。这使得对每个索引的检查更加模糊,因此我认为使用下面的流不是更好。

流并没有那么糟糕: 字符串的完整副本到字符串流中。但对于小字符串来说不是什么大问题。

std::tuple<std::string, std::vector<int>> ck2(std::string const& info)

    std::stringstream is(info);
    std::string         name;
    std::vector<int>    index;

    if (std::getline(is, name, '[')) 
        is.putback('[');
        name.erase(std::remove(std::begin(name), std::end(name), ' '), std::end(name));

        int   value;
        char  b1;
        char  b2;
        while(is >> b1 >> value >> b2 && b1 == '[' && b2 == ']') 
            index.push_back(value);
        
    
    return std::make_tuple(name, index);

【讨论】:

谢谢。我更新了我原来的帖子。我同意你的看法。我看不到使用正则表达式解决这个问题的好处。 那是 std::sscanf(),假设您包含 C++ 标头 &lt;cstdio&gt;。不建议将兼容性标头 &lt;stdio.h&gt; 用于新代码。 @TobySpeight 是的。有标志可以检查吗? no way 可以使用 GCC 捕获该错误。唯一的useful suggestion 是在 Solaris 上使用 Oracle 的 Studio 编译器构建的。 (可悲的是,我不再有任何 Solaris 机器,而且我从来没有那个编译器......)【参考方案2】:

我的回答与 Martin York 的回答非常相似,但我改用了 stl。

#include <iostream>
#include <vector>
#include <string>
#include <tuple>

std::tuple<std::string, std::vector<int>> getNameIndices(std::string s)

    std::vector<int> indices;

    // The name must end at the first '['
    size_t pos = s.find("[");
    // If we can't find that, then it isn't a valid string - return empty
    if (pos == std::string::npos)
        return std::make_tuple("", indices);

    // Get the name and remove it from the string
    std::string name = s.substr(0, pos);
    s.erase(0, pos + 1);

    size_t begin = 0;
    // Keep looping as long as we can find the start of a new index
    while ((pos = s.find("]")) != std::string::npos)
    
        // Begin is the position of the '[', pos is the ']': Get the text between them
        std::string tmp = s.substr(begin, pos - begin);
        indices.push_back(stoi(tmp));
        // Remove the characters that were matched, and update 'begin'
        s.erase(0, pos + 1);
        begin = s.find("[") + 1;
    

    // Return the name and indices in a vector
    return std::make_tuple(name, indices);


void main()

    std::string s = "foo[500][12][2][13]";

    auto b = getNameIndices(s);

    std::cout << "Name: " << std::get<0>(b) << std::endl;
    for (int i : std::get<1>(b))
    
        std::cout << "\t" << i << std::endl;
    

【讨论】:

如果你打算使用 STL,你应该使用 std::stringstreamoperator &gt;&gt;std::getline() 来提取部分。 @MartinYork 看过您的 STL 解决方案后,我同意:这种方式看起来更干净、更好。

以上是关于c ++:解析包含表达式“访问多维数组”的字符串的主要内容,如果未能解决你的问题,请参考以下文章

09_c++面向对象

C++ 多线程服务器帮助

在 Python 中解析 .c/.cpp/.py 源文件以获取包含的函数列表

解析C中条件编译,头文件包含知识,以及 #/## 的运用

使用 C 使用多线程从文本文件中解析单词

c/c++源文件为何要包含自己的头文件?(编译器检查定义和声明的一致性)