【问题标题】:How do I compare a section of a string without copying?如何在不复制的情况下比较字符串的一部分?
【发布时间】:2019-08-06 15:31:04
【问题描述】:

我有一个正在迭代的长字符串,并且在每次迭代时,我都会将字符串的一部分与一个常量进行比较并存储字符串的某些部分。在我的实际代码中,这段代码运行了数百万次,是主要的瓶颈。我认为这是由于过度使用std::string::substr

#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
    std::vector<std::string> out0;
    std::map<std::string, std::string> out;

    size_t pos = str.find(',');

    // loop over the string, collecting "key=value" pairs
    while (pos < str.size() - 1) {
        if (str.substr(pos + 1, 2) == "0=") {
            auto newPos = str.find(',', pos + 3);
            out0.push_back(str.substr(pos + 3, newPos - pos - 3);
            pos = newPos;
        } else {
            size_t eqPos = str.find('=', pos + 1);
            auto newPos = str.find(',', eqPos + 1);
            out[str.substr(pos + 1, eqPos - pos - 1)] = str.substr(eqPos + 1, newPos - eqPos - 1);
        }
    }

    // print out the data structures (this doesn't happen in my actual code)
    std::cout << "out0:";
    for (auto& entry : out0) {
        std::cout << ' ' << entry;
    }
    std::cout << std::endl;

    std::cout << "out:";
    for (auto it : out) {
        std::cout << ' ' << it->first << '=' << it->second;
    }
}

这是我的问题:

  • 如何在不执行复制且不为每个字符编写比较的情况下对字符串执行比较,例如str[pos + 1] == '0' &amp;&amp; str[pos + 2] == '=' &amp;&amp; ...?
  • 如何存储对子字符串的引用,而不是每次添加到 out0out 时都进行复制?

这可能是使用char * 的好案例,但我之前从未使用过。

编辑:

不幸的是,我只有 C++11;否则,std::string_view 是最好的答案。有没有办法在没有std::string_view的情况下完成引用的存储?

【问题讨论】:

  • 使用std::char_traits&lt;char&gt;::compare直接比较字符序列。见en.cppreference.com/w/cpp/string/char_traits/compare。我的回答中有一个例子。
  • 我还在寻找能够在不复制的情况下在字符串中存储对段的引用的能力,我只通过存储char*size_t 来实现。感谢您的全面回答!
  • @Kyle 你可以使用std::pair

标签: c++ string c++11


【解决方案1】:

如果你有 C++17,你可以使用string_view 这样:(未经测试的代码):

string_view sv{str.data() + pos, 2};
if (sv == "0=") ...

没有副本。甚至(一口气):

if (string_view{str.data() + pos, 2} == "0=") ...

如果没有string_view,可以使用char_traits

if (std::char_traits<char>::compare(str.data() + pos, "0=", 2) == 0) ...

【讨论】:

    【解决方案2】:

    使用std::string_view 而不是std::string 作为out 的键和值。 std::string_view 持有指向字符串的指针,以及字符串的大小,所以它的重量很轻。这使您可以提取所需的信息,而无需复制字符串中的任何字符以及创建这些字符串的任何潜在内存分配。

    您需要做的是从std::string 获取string_view,然后使用该string_view 获取您需要的所有子字符串。

    【讨论】:

      【解决方案3】:

      std::string 具有将 const char* 子字符串作为输入的 compare() 方法。您不需要使用std::string::substr() 来比较子字符串,例如:

      #include <iostream>
      #include <map>
      #include <string>
      #include <vector>
      
      int main() {
          std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
          std::vector<std::string> out0;
          std::map<std::string, std::string> out;
      
          size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
      
          // loop over the string, collecting "key=value" pairs
          while (startPos < str.size()){
              nameStart = startPos;
              delimPos = str.find_first_of("=,", startPos, 2);
              if (delimPos == std::string::npos) {
                  nameEnd = valueStart = valueEnd = str.size();
              }
              else {
                  nameEnd = delimPos;
                  if (str[delimPos] == '=') {
                      valueStart = nameEnd + 1;
                      valueEnd = str.find(',', valueStart);
                      if (valueEnd == std::string::npos) {
                          valueEnd = str.size();
                      }
                  }
                  else {
                      valueStart = valueEnd = nameEnd;
                  }
              }
      
              // TODO: if needed, adjust name(Start|End) and value(Start|End) to
              // ignore leading/trailing whitespace around the name and value
              // substrings...
      
              if (str.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
                  out0.push_back(str.substr(valueStart, valueEnd - valueStart));
              } else {
                  out[str.substr(nameStart, nameEnd - nameStart)] = str.substr(valueStart, valueEnd - valueStart);
              }
      
              startPos = valueEnd + 1;
          }
      
          // print out the data structures
          std::cout << "out0:";
          for (auto& entry : out0) {
              std::cout << ' ' << entry;
          }
          std::cout << std::endl;
      
          std::cout << "out:";
          for (auto it : out) {
              std::cout << ' ' << it->first << '=' << it->second;
          }
      }
      

      输出:

      out0: 我的 输出:1=逗号 2=分隔 3=重复 7=ID

      Live Demo

      您可以更进一步,完全不使用substr(),方法是根本不在std::vectorstd::map 中存储std::string 值,而是存储std::pair&lt;char*, size_t&gt;

      #include <iostream>
      #include <map>
      #include <string>
      #include <vector>
      #include <utility>
      
      using StrView = std::pair<const char*, size_t>;
      
      StrView makeStrView(const char *str, size_t size) {
          return std::make_pair(str, size);
      }
      
      struct compareStrView {
          bool operator()(const StrView &lhs, const StrView &rhs) const {
              if (lhs.second == rhs.second)
                  return (std::char_traits<char>::compare(lhs.first, rhs.first, lhs.second) < 0);
              return (lhs.second < rhs.second);
          }
      };
      
      std::ostream& operator<<(std::ostream &os, const StrView &rhs) {
          return os.write(rhs.first, rhs.second);
      }
      
      int main() {
          std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
          std::vector<StrView> out0;
          std::map<StrView, StrView, compareStrView> out;
      
          size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
      
          // loop over the string, collecting "key=value" pairs
          while (startPos < str.size()){
              nameStart = startPos;
              delimPos = str.find_first_of("=,", startPos, 2);
              if (delimPos == std::string::npos) {
                  nameEnd = valueStart = valueEnd = str.size();
              }
              else {
                  nameEnd = delimPos;
                  if (str[delimPos] == '=') {
                      valueStart = nameEnd + 1;
                      valueEnd = str.find(',', valueStart);
                      if (valueEnd == std::string::npos) {
                          valueEnd = str.size();
                      }
                  }
                  else {
                      valueStart = valueEnd = nameEnd;
                  }
              }
      
              // TODO: if needed, adjust nameStart/End and valueStartEnd to
              // ignore leading/trailing whitespace around the name and value
              // substrings...
      
              if (str.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
                  out0.push_back(makeStrView(&str[valueStart], valueEnd - valueStart));
              } else {
                  out[makeStrView(&str[nameStart], nameEnd - nameStart)] = makeStrView(&str[valueStart], valueEnd - valueStart);
              }
      
              startPos = valueEnd + 1;
          }
      
          // print out the data structures
          std::cout << "out0:";
          for (auto& entry : out0) {
              std::cout << ' ' << entry;
          }
          std::cout << std::endl;
      
          std::cout << "out:";
          for (auto &it : out) {
              std::cout << ' ' << it.first << '=' << it.second;
          }
      }
      

      输出:

      out0: 我的 输出:1=逗号 2=分隔 3=重复 7=ID

      Live Demo

      在 C++17 中,您可以使用 std::string_view 代替:

      #include <iostream>
      #include <map>
      #include <string>
      #include <vector>
      #include <string_view>
      
      int main() {
          std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
          std::string_view sv(str);
          std::vector<std::string_view> out0;
          std::map<std::string_view, std::string_view> out;
      
          size_t startPos = 0, delimPos, nameStart, nameEnd, valueStart, valueEnd;
      
          // loop over the string, collecting "key=value" pairs
          while (startPos < sv.size()){
              nameStart = startPos;
              delimPos = sv.find_first_of("=,", startPos, 2);
              if (delimPos == std::string_view::npos) {
                  nameEnd = valueStart = valueEnd = sv.size();
              }
              else {
                  nameEnd = delimPos;
                  if (sv[delimPos] == '=') {
                      valueStart = nameEnd + 1;
                      valueEnd = sv.find(',', valueStart);
                      if (valueEnd == std::string_view::npos) {
                          valueEnd = sv.size();
                      }
                  }
                  else {
                      valueStart = valueEnd = nameEnd;
                  }
              }
      
              // TODO: if needed, adjust nameStart/End and valueStartEnd to
              // ignore leading/trailing whitespace around the name and value
              // substrings...
      
              if (sv.compare(nameStart, nameEnd - nameStart, "0", 1) == 0) {
                  out0.push_back(sv.substr(valueStart, valueEnd - valueStart));
              } else {
                  out[sv.substr(nameStart, nameEnd - nameStart)] = sv.substr(valueStart, valueEnd - valueStart);
              }
      
              startPos = valueEnd + 1;
          }
      
          // print out the data structures
          std::cout << "out0:";
          for (auto& entry : out0) {
              std::cout << ' ' << entry;
          }
          std::cout << std::endl;
      
          std::cout << "out:";
          for (auto &it : out) {
              std::cout << ' ' << it.first << '=' << it.second;
          }
      }
      

      【讨论】:

        【解决方案4】:

        由于人们发布了 std::string_view,这里是普通的旧 C 指针版本。

        (虽然没有测试,但它会给你的想法)

        见下文:

        std::string str("0=My,1=comma,2=separated,3=string,0=with,3=repeated,7=IDs");
        std::string substr("test");
        .
        . Inside some function
        .
        const char *str_p = str.c_str();        // String you want to compare with a substring
        const char *substr_p = substr.c_str();  // Your substring
        size_t str_len = str.length();
        size_t substr_len = substr.length();
        bool comparison_result = true;
        for(size_t i = 0; i < str_len - substr_len; i++) {
            for(size_t j = 0; j < substr_len; j++) {
                if(*(str_p + i + j) != *(substr_p + j)) {
                    comparison_result = false;
                    break;
                }
                if (j == substr_len - 1) { // We can only reach here when substring is hit
                    comparison_result = true;
                    i = str_len - substr_len;
                    break;
                }
            }
        }
        return comparison_result;
        

        编辑:

        由于@Toby Speight 在 cmets 中的建议(我觉得这非常好),我也在实施 std::memcmp() 版本。在这种情况下,内部循环变为:

        .
        . Inside some function
        .
        const char *str_p = str.c_str();        // String you want to compare with a substring
        const char *substr_p = substr.c_str();  // Your substring
        size_t str_len = str.length();
        size_t substr_len = substr.length();
        bool comparison_result = false;
        for(size_t i = 0; i < str_len - substr_len; i++) {
            if(std::memcmp(str_p + i, substr_p, substr_len) == 0) {
                comparison_result = true;
                break;
            }
        }
        return comparison_result;
        

        编辑:

        我们收到了另一个请求,这次来自@Alexander Zhang,让我们实现它:

        .
        . Inside some function
        .
        const char *str_p = str.c_str();        // String you want to compare with a substring
        const char *substr_p = substr.c_str();  // Your substring
        size_t str_len = str.length();
        size_t substr_len = substr.length();
        bool comparison_result = false;
        for(size_t i = 0; i < str_len - substr_len; i++) {
            if(std::memcmp(&str_p[i], &substr_p[0], substr_len) == 0) {
                comparison_result = true;
                break;
            }
        }
        return comparison_result;
        

        【讨论】:

        • 使用std::memcmp() 可能比在那里编写自己的内部循环更好。
        • 您也可以将下标符号与指针一起使用。不需要显式指针算法。
        • 不需要comparison_result 变量 - 我们可以在找到匹配项后立即返回true,如果循环终止则返回false
        • 我添加它的原因是因为:它是一个更简单的约定,只在函数末尾使用return var;。因为,有时您可以通过在循环后添加一个小检查来修改您的函数,然后突然之间,您的函数不会执行您的扩展。但我猜这有点个人喜好。
        【解决方案5】:

        您可以尝试使用正则表达式来拆分值对元组。

        虽然没有测试过是否更快

        这个表达式应该可以解决问题,只需获取所有匹配项(所有对)

        (?:(\d)+=(?:([^,]*),?))*?

        https://regex101.com/r/PDZMq0/1

        【讨论】:

        • 我认为有些用户对您的回答投了反对票,因为我的问题不是关于拆分行,而是关于存储和搜索字符串。
        • 如果我理解正确/足够清楚,您想要获取键/值对。将字符串与该表达式匹配后,您可以检索组
        猜你喜欢
        • 2012-05-05
        • 2016-01-17
        • 2021-06-11
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        • 1970-01-01
        • 2011-02-20
        • 1970-01-01
        相关资源
        最近更新 更多