如何计算 QString 开头的重复字符?

How to count recurring characters at the beginning of a QString?

我正在处理一个行列表,我需要计算开头出现的哈希值。

#  item 1
## item 1, 1
## item 1, 2
#  item 2

等等。

如果每一行都是一个 QString,我如何 return 字符串开头出现的哈希数?

QString s("### foo # bar ");
int numberOfHashes = s.count("#"); // Answer should be 3, not 4

这里我使用标准算法 find_if_not 来获取第一个不是散列的字符的迭代器。然后我 return 从字符串的开始到那个迭代器的距离。

int number_of_hashes(QString const& s)
{
    auto it = std::find_if_not(std::begin(s), std::end(s), [](QChar c){return c == '#';});
    return std::distance(std::begin(s), it);
}

编辑find_if_not函数只接受一元谓词,而不是值,所以你必须传递一个lambda谓词。

琐碎的:

int number_of_hashes(const QString &s) {
    int i, l = s.size();
    for(i = 0; i < l && s[i] == '#'; ++i);
    return i;
}

在其他语言(主要是解释型语言)中,您不得不担心对字符进行迭代,因为它很慢,并且将所有内容委托给库函数(通常用 C 编写)。在 C++ 中,迭代在性能方面非常好,因此脚踏实地的 for 循环就可以了。


为了好玩,我 a small benchmark 将这个简单的方法与来自 OP 的 QRegularExpression 方法进行了比较,可能带有缓存的 RE 对象。

#include <QCoreApplication>
#include <QString>
#include <vector>
#include <QElapsedTimer>
#include <stdlib.h>
#include <iostream>
#include <QRegularExpression>

int number_of_hashes(const QString &s) {
    int i, l = s.size();
    for(i = 0; i < l && s[i] == '#'; ++i);
    return i;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    const int count = 100000;
    std::vector<QString> ss;
    for(int i = 0; i < 100; ++i) ss.push_back(QString(rand() % 10, '#') + " foo ## bar ###");
    QElapsedTimer t;
    t.start();
    unsigned tot = 0;
    for(int i = 0; i < count; ++i) {
        for(const QString &s: ss) tot += number_of_hashes(s);
    }
    std::cerr<<"plain loop: "<<t.elapsed()*1000./count<<" ns\n";
    t.restart();
    for(int i = 0; i < count; ++i) {
        for(const QString &s: ss) tot += QRegularExpression("^[#]*").match(s).capturedLength();
    }
    std::cerr<<"QRegularExpression, rebuilt every time: "<<t.elapsed()*1000./count<<" ns\n";

    QRegularExpression re("^[#]*");
    t.restart();
    for(int i = 0; i < count; ++i) {
        for(const QString &s: ss) tot += re.match(s).capturedLength();
    }
    std::cerr<<"QRegularExpression, cached: "<<t.elapsed()*1000./count<<" ns\n";
    return tot;    
}

正如预期的那样,基于 QRegularExpression 慢了两个数量级

plain loop: 0.7 ns
QRegularExpression, rebuilt every time: 75.66 ns
QRegularExpression, cached: 24.5 ns
int numberOfHashes = 0;
int size = s.size();
QChar ch('#');
for(int i = 0; (i < size) && (s[i] == ch); ++i) {
    ++numberOfHashes;
} 

Qt 方法,利用 QString::indexOf(..):

QString s("### foo # bar ");
int numHashes = 0;

while ((numHashes = s.indexOf("#", numHashes)) == numHashes) {
    ++numHashes;
} // numHashes == 3
int QString::indexOf(const QString &str, int from = 0, 
                     Qt::CaseSensitivity cs = Qt::CaseSensitive) const

Returns the index position of the first occurrence of the string str in this string, searching forward from index position from. Returns -1 if str is not found.

从索引 0 开始,在字符串 s 中搜索第一次出现的 #,然后使用谓词来测试这次出现是否在索引 [=17] =].如果未终止,则继续索引 1,依此类推。

但是,这不会缩短最终可能的完整字符串搜索。如果在其预期位置未找到散列,则在最终失败的谓词检查之前,将完全搜索该字符串(或直到第一个散列位于错误位置)一次。

另一种方式:

int beginsWithCount(const QString &s, const QChar c) {
  int n = 0;
  for (auto ch : s)
    if (c == ch) n++; else break;
  return n;
}

没有 for 循环的解决方案:

QString s("### foo # bar ");
int numberOfHashes = QRegularExpression("^[#]*").match(s).capturedLength();