Как посчитать повторяющиеся символы в начале строки QString?

Я имею дело со списком строк, и мне нужно сосчитать хэши, которые появляются в начале.

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

и так далее.

Если каждая строка является QString, как я могу вернуть количество хэшей, встречающихся в начале строки?

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

6 ответов

Решение

Тривиально:

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 петля сделает.


Ради интереса я сделал небольшой тест, сравнивая этот тривиальный метод с QRegularExpression один из OP, возможно с кешированным объектом 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

Здесь я использую стандартный алгоритм find_if_not чтобы получить итератор для первого символа, который не является хешем. Затем я возвращаю расстояние от начала строки до этого итератора.

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 Функция принимает только унарный предикат, а не значение, поэтому вы должны передать лямбда-предикат.

int numberOfHashes = 0;
int size = s.size();
QChar ch('#');
for(int i = 0; (i < size) && (s[i] == ch); ++i) {
    ++numberOfHashes;
} 

Решение без цикла for:

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

Еще один способ:

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

Подход 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

Возвращает позицию индекса первого вхождения строки str в этой строке, поиск вперед от позиции индекса from, Возвращает -1 если str не найден.

Начиная с индекса 0, строка s ищется первое вхождение # и затем использовать предикат для проверки того, находится ли это вхождение в индексе 0, Если не прекращено, продолжается с индексом 1, и так далее.

Однако это не приведет к короткому замыканию при окончательном поиске по всей строке. В случае, если хеш не найден в его ожидаемой позиции, перед последней ошибочной проверкой предиката строка будет найдена полностью (или до первого хеша в неправильной позиции) один раз.

Простое решение, однако, работает только для представленного шаблона ввода:

int numberOfHashes = s.split(" ").first().count();
Другие вопросы по тегам