Устали писать циклы для подсчета элементов в контейнерах C++? Подсчитать элементы по конкретным условиям легко универсальным алгоритмом std::count_if.

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

Что такое count_if?

std::count_if  —  шаблон функции в стандартной библиотеке C++, которым подсчитывается количество элементов в диапазоне, удовлетворяющих заданному условию. Это часть заголовка <algorithm> для работы с любым контейнером, которым предоставляются однонаправленные итераторы.

Вот базовый синтаксис:

template<class InputIt, class UnaryPredicate>
typename iterator_traits<InputIt>::difference_type
count_if(InputIt first, InputIt last, UnaryPredicate p);

Не пугайтесь жаргона этого шаблона. На практике count_if прост, с ним код удобнее для восприятия и эффективнее.

Приступим к работе с count_if

Вот простой пример:

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int evenCount = std::count_if(numbers.begin(), numbers.end(),
[](int n) { return n % 2 == 0; });

std::cout << "Number of even integers: " << evenCount << std::endl;
return 0;
}

Здесь подсчитывается количество четных целых чисел в векторе. Лямбда-функция [](int n) { return n % 2 == 0; }  —  это предикат, которым возвращается true для четных чисел и false для нечетных.

Предикат

Предикат  —  основа count_if. Это функция или объект-функция, которой принимается один аргумент и возвращается логическое значение. Вот способы определения предикатов:

  1. Лямбда-выражения  —  как в примере выше.
  2. Указатели функции.
  3. Функциональные объекты, или функторы.

Вот пример использования указателя функции:

bool isPositive(int n) {
return n > 0;
}

int main() {
std::vector<int> numbers = {-2, -1, 0, 1, 2, 3, 4, 5};
int positiveCount = std::count_if(numbers.begin(), numbers.end(), isPositive);
std::cout << "Number of positive integers: " << positiveCount << std::endl;
return 0;
}

А теперь функтора:

class DivisibleBy {
private:
int divisor;
public:
DivisibleBy(int d) : divisor(d) {}
bool operator()(int n) const {
return n % divisor == 0;
}
};

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int divisibleBy3Count = std::count_if(numbers.begin(), numbers.end(), DivisibleBy(3));
std::cout << "Numbers divisible by 3: " << divisibleBy3Count << std::endl;
return 0;
}

Механизм работы count_if

Алгоритмом count_if перебирается заданный диапазон, к каждому элементу которого применяется предикат. При этом всякий раз, когда предикатом возвращается true, счетчик увеличивается. Проиллюстрируем упрощенной реализацией:

template<class InputIt, class UnaryPredicate>
typename std::iterator_traits<InputIt>::difference_type
count_if_impl(InputIt first, InputIt last, UnaryPredicate p) {
typename std::iterator_traits<InputIt>::difference_type count = 0;
for (; first != last; ++first) {
if (p(*first)) {
++count;
}
}
return count;
}

Здесь показано, за счет чего count_if столь эффективен: за один его проход по контейнеру предикат применяется к каждому элементу ровно один раз.

Реальные применения

С работой count_if разобрались, изучим практические применения его универсальности.

Анализ текстовых данных

Инструментом анализа текста подсчитывается количество слов, которые соответствуют неким критериям. Слова, чья длина превышает конкретное значение, подсчитываются в count_if так:

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

int main() {
std::vector<std::string> words = {"the", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"};

int longWordCount = std::count_if(words.begin(), words.end(),
[](const std::string& word) { return word.length() > 3; });

std::cout << "Number of words longer than 3 characters: " << longWordCount << std::endl;
return 0;
}

Фильтрация данных с датчиков

Во встраиваемых системах или приложениях интернета вещей анализируются данные датчиков. Подсчитаем, например, количество температурных показаний в пределах заданного диапазона:

#include <algorithm>
#include <vector>
#include <iostream>

struct TemperatureReading {
double celsius;
// Другие поля вроде временнóй метки, идентификатора датчика и т. д.
};

bool isWithinOperatingRange(const TemperatureReading& reading) {
return reading.celsius >= 20.0 && reading.celsius <= 25.0;
}

int main() {
std::vector<TemperatureReading> readings = {
{18.5}, {20.1}, {22.3}, {25.0}, {26.7}, {23.1}, {19.8}
};

int withinRangeCount = std::count_if(readings.begin(), readings.end(), isWithinOperatingRange);

std::cout << "Number of readings within operating range: " << withinRangeCount << std::endl;
return 0;
}

Анализ финансовых данных

В финансовых приложениях подсчитываются сделки, соответствующие конкретным критериям. Вот как при помощи count_if анализируются, например, сделки по акциям:

#include <algorithm>
#include <vector>
#include <iostream>

struct Trade {
std::string symbol;
double price;
int volume;
};

int main() {
std::vector<Trade> trades = {
{"AAPL", 150.25, 100},
{"GOOGL", 2750.50, 10},
{"MSFT", 305.75, 50},
{"AAPL", 151.00, 200},
{"AMZN", 3300.00, 5}
};

auto isLargeAppleTrade = [](const Trade& t) {
return t.symbol == "AAPL" && t.volume > 100;
};

int largeAppleTradeCount = std::count_if(trades.begin(), trades.end(), isLargeAppleTrade);

std::cout << "Number of large Apple trades: " << largeAppleTradeCount << std::endl;
return 0;
}

Передовые методы и рекомендации

Несмотря на простоту count_if, следует учитывать ряд приемов и рекомендаций.

Применение count_if с пользовательскими типами

При работе с пользовательскими типами убедитесь, что тип обрабатывается предикатом корректно. Вот пример с пользовательским классом Person:

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

class Person {
public:
Person(std::string n, int a) : name(std::move(n)), age(a) {}
std::string name;
int age;
};

int main() {
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
{"David", 28}
};

int adultsCount = std::count_if(people.begin(), people.end(),
[](const Person& p) { return p.age >= 18; });

std::cout << "Number of adults: " << adultsCount << std::endl;
return 0;
}

Сочетание count_if с другими алгоритмами

Для операций посложнее count_if комбинируют с другими алгоритмами. Например, с std::transform  —  для подсчета элементов после применения преобразования:

#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>

int main() {
std::vector<double> values = {-1.5, 2.0, -3.7, 4.2, -5.1};
std::vector<double> absolutes(values.size());

std::transform(values.begin(), values.end(), absolutes.begin(),
[](double v) { return std::abs(v); });

int countAbove3 = std::count_if(absolutes.begin(), absolutes.end(),
[](double v) { return v > 3.0; });

std::cout << "Number of values with absolute value > 3: " << countAbove3 << std::endl;
return 0;
}

Производительность

Хотя count_if в целом эффективен, имейте в виду:

  1. В небольших контейнерах простой цикл быстрее из-за меньших накладных расходов.
  2. В больших контейнерах, чтобы потенциально повысить производительность, count_if распараллеливается при помощи std::execution::par, который доступен в версии C++17 и новее.
  3. Если предикат вычислительно дорог, оптимизируйте его отдельно.

Заключение

С count_if упрощается подсчет элементов, соответствующих конкретным критериям, отчего код становится более удобным для восприятия и сопровождаемым. Изучив внутренние механизмы и реальные применения count_if, вы сможете эффективно использовать его в проектах  —  будь то анализ текста, обработка данных с датчиков или финансовый анализ.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: C++ count_if: A Deep Dive

Предыдущая статьяПереход с VS Code на Neovim: повысьте свою продуктивность 
Следующая статьяДобавление новостных тем в приложение TrendNow. Часть 2