【问题标题】:How to check if elements of std::vector<std::string> start with certain sub-string?如何检查 std::vector<std::string> 的元素是否以某个子字符串开头?
【发布时间】:2019-12-29 14:01:52
【问题描述】:

我有一个非常大的std::vector v std::vector&lt;std::string&gt; v。现在我想比较向量中的哪些元素以某个子字符串开头 str。最快的方法是什么?

我正在考虑一个 for 循环,它迭代地将 v 的每个元素的开头与子字符串 str 进行比较。我第一次尝试

std::string substring = "bla";
for (long unsigned int i = 0; i < v.size(); i++)
{
    if (!strncmp(v[i].c_str(), substring.c_str(), substring.size())) 
    {
        std::cout << "Item found: " << v[i] << std::endl;
    }
}

混合在一起,我对此并不满意。

还有什么更好的选择?

【问题讨论】:

  • 只需这样做:if ( v[i].substr(0, substring.size()) == substring ) { /* ... */ } 用于字符串比较。

标签: c c++ c++ c++11 stdvector stdstring c++-standard-library


【解决方案1】:

你完全可以写一个代码。

如果你想找到所有满足条件的元素,你不能避免遍历整个向量。 但是您可以使用更好的 range-based for-loop 而不是基于索引的循环来遍历向量,并检查 str.find(substring) == 0(credits @PiotrSkotnicki)。

下面是示例代码: (See online)

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

int main()
{
    const std::string substring{ "bla" };
    std::vector<std::string> vecString{ {"bllll"}, {"bllll"}, {"blasomething"} };
    // iterate through the vector by range based for-loop
    // here `auto` deduded to `std::string` as you have vector of strings(i.e. `vecString`)
    for (const auto& str : vecString)
    {
        if (str.find(substring) == 0) {
            std::cout << str << " is a match\n";
            // do something more with str
        }
    }
    return 0;
}

或者使用std::for_each,连同一个lambda函数,您可以编写以下代码。在此处阅读有关 lambda 的更多信息:What is a lambda expression in C++11? (See online)

#include <algorithm> // std::for_each

std::for_each(std::cbegin(vecString), std::cend(vecString), [&substring](const auto& str)
{
    if (str.find(substring) == 0)
    {
        std::cout << str << " is a match\n";
        // do something more with str
    }
});

如果你只对字符串的向量中的第一个匹配项感兴趣,使用标准算法std::find_if如下

#include <algorithm> // std::find_if

const auto iter = std::find_if(std::cbegin(vecString), std::cend(vecString),
    [&substring](const auto& str) {
        return str.find(substring) == 0;
    }
);
if (iter != std::cend(vecString))
{
    // do something
}

【讨论】:

  • 你仍然需要循环。这只会返回第一个匹配项。
  • @DimChtz 错过了“所有元素”的部分。好的,那么只需基于范围的循环或std::for_each。会相应更新
  • 您能在您的代码中添加一些 cmets 吗?我不熟悉诸如for (const auto&amp; str : vecString)const auto check = [&amp;substring](const auto&amp; str) 之类的行。谢谢!
  • 如果我只对子字符串与数组中字符串的开头匹配感兴趣时,我是否需要str.find()
  • @ALX23z 这看起来很有希望:quick-bench.com/PuL3ggsdyADAUL47Al3SPKgHRog(希望我做得正确)。我建议将其证明为答案,以便未来的读者可以从中受益。我很乐意把它加起来。 ?
【解决方案2】:

如果你有一个未排序的容器,你在时间复杂度上不会比 O(n) 更好,这意味着以线性方式迭代整个容器(即 for 循环)。如果您的容器已排序(例如 std::set 而不是 std::vector),您会得到 O(log n),这会好很多(二分搜索)。

在 C++17 之前,我想不出比你更好的解决方案(因为通过 std::string::substr 创建子字符串意味着不必要地复制子字符串)。但是 C++17 引入了 std::string_view,它不会进行任何复制。启用编译器优化后应该没有明显的性能差异。

std::vector<std::string> v { "abcd", "abcdefg", "aaaabbbb", "abc", "ab"};
std::string_view query = "abc";

for (auto const& str : v) 
{
    if (str.size() < query.size())
        continue;

    auto probe = std::string_view(str).substr(0, query.size());
    if (query == probe)
        std::cout << "Item found: " << str << "\n";        
}

Live example

这里是std::set 版本,可以更快地搜索:

std::set<std::string> v { "abcd", "abcdefg", "aaaabbbb", "abc", "ab"};
std::string query = "abc";

for (auto it = v.lower_bound(query); it != v.end(); ++it)
{
    auto probe = std::string_view(*it).substr(0, query.size());
    if (query == probe)
        std::cout << "Item found: " << *it << "\n";     
    else
        break;
}

Live example

【讨论】:

    【解决方案3】:

    你可以使用c++20 std::string_view::start_with:

    std::vector<std::string> v = {...};
    std::string_view prefix = "bla";
    for (std::string_view sv : v)
        if (sv.starts_with(prefix))
            std::cout << "Item found: " << sv << std::endl;
    

    【讨论】:

      猜你喜欢
      • 2010-12-25
      • 2010-10-30
      • 1970-01-01
      • 2011-01-14
      • 2011-09-10
      • 1970-01-01
      • 2020-12-19
      • 1970-01-01
      相关资源
      最近更新 更多