如何在不复制的情况下比较字符串的一部分?

How do I compare a section of a string without copying?

本文关键字:比较 字符串 一部分 情况下 复制      更新时间:2023-10-16

我有一个长字符串,我正在迭代,在每次迭代时,我将字符串的一部分与常量进行比较,并存储字符串的某些部分。在我的实际代码中,这段代码运行了数百万次,是主要的瓶颈。我认为这是由于过度使用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' && str[pos + 2] == '=' && ...
  • 如何存储对子字符串的引用,而不是每次添加到out0out时都复制?

这可能是使用char *的一个很好的例子,但我以前从未使用过它。

编辑:

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

如果您有 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) ...

由于人们已经发布了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在评论中的建议(我觉得非常好(,我也在实现一个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,我们来实现一下:

.
. 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;

out 的键和值使用 std::string_view 而不是 std::stringstd::string_view包含指向字符串的指针和字符串的大小,因此它的重量非常轻。 这使您可以提取所需的信息,但不必复制字符串中的任何字符以及创建这些字符串的任何潜在内存分配。

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

std::stringcompare()个方法,这些方法将const char*子字符串作为输入。 你不需要使用 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

现场演示

您可以更进一步,通过根本不在std::vectorstd::map中存储std::string值来完全消除substr()的使用,而是存储std::pair<char*, size_t>

#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

现场演示

在 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;
    }
}

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

虽然还没有测试过是否有更快的速度

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

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

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