Устали писать циклы для подсчета элементов в контейнерах 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. Это функция или объект-функция, которой принимается один аргумент и возвращается логическое значение. Вот способы определения предикатов:
- Лямбда-выражения — как в примере выше.
- Указатели функции.
- Функциональные объекты, или функторы.
Вот пример использования указателя функции:
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 в целом эффективен, имейте в виду:
- В небольших контейнерах простой цикл быстрее из-за меньших накладных расходов.
- В больших контейнерах, чтобы потенциально повысить производительность,
count_ifраспараллеливается при помощиstd::execution::par, который доступен в версии C++17 и новее. - Если предикат вычислительно дорог, оптимизируйте его отдельно.
Заключение
С count_if упрощается подсчет элементов, соответствующих конкретным критериям, отчего код становится более удобным для восприятия и сопровождаемым. Изучив внутренние механизмы и реальные применения count_if, вы сможете эффективно использовать его в проектах — будь то анализ текста, обработка данных с датчиков или финансовый анализ.
Читайте также:
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: C++ count_if: A Deep Dive





