В C++20
std::ranges
можно ожидать получения views::group_by
1 . Это может быть очень удобно, но я нашел проблему во время игры с ним. Из руководства Эрика Ниблера мы можем прочитать, что это " По существу, views::group_by
группирует смежные элементы вместе с двоичным предикатом. ". Давайте рассмотрим пример. У меня есть std::vector
некоторых int
с, и я хочу сгруппировать в два диапазона - представляющих четные и нечетные числа. Мой первоначальный подход состоял в том, чтобы просто сделать:
int main() {
using namespace ranges;
std::vector<int> ints = {3, 9, 12, 10, 7, 5, 1, 4, 8};
for (auto rng : ints | views::group_by(
[](auto lhs, auto rhs) {
const bool leftEven = lhs % 2 == 0;
const bool rightEven = rhs % 2 == 0;
return (leftEven && rightEven) || (!leftEven && !rightEven);
})) {
std::cout << rng << '\n';
}
}
Но это не может работать. Или, другими словами, это будет работать, но даст неожиданные (для некоторых, я полагаю) результаты для тех, кто знаком с подобными операциями в других языках (или даже API). Вывод этой программы:
[3,9]
[12,10]
[7,5,1]
[4,8]
Четные и нечетные числа не все сгруппированы - это потому, что они не все смежные. 3
и 9
спарены вместе, потому что они ood и смежные . Точно так же (кроме того, чтобы быть даже ) 12
и 10
. Но 7
, 5
и 1
создадут отдельную группу - они не будут сгруппированы с 3
и 9
, и это не то, чего я бы хотел или ожидал.
Чтомы, конечно, могли бы сделать это для partition
вектора ints
, чтобы упорядочить элементы так, чтобы четные и четные составляли две группы. Проблема в том, что в диапазонах нет views::
partition
. Это оставляет меня с двумя вариантами, где ни один из них не особенно мне нравится:
1. std</s>ranges::partition
перед просмотром вектора:
Вызов:
ranges::partition(ints, [](auto elem) { return elem % 2 == 0; });
непосредственно перед нашим диапазоном на основе for
петли, и у нас есть желаемый результат:
[8,4,12,10]
[7,5,1,9,3]
Мне это не нравится, потому что у него нет компоновки - один из ключевых факторов ranges
. Я не хочу разделять вектор, если честно. Я хочу напечатать его элементы в двух группах - четные и нечетные.
2. Используйте actions::sort
и сортируйте вектор с помощью четно-нечетного компаратора:
int main() {
using namespace ranges;
std::vector<int> ints = {3, 9, 12, 10, 7, 5, 1, 4, 8};
auto evens_first = [](auto lhs, auto rhs) { return lhs % 2 == 0 && rhs % 2 != 0; };
for (auto rng : (ints |= actions::sort(evens_first)) | views::group_by(
[](auto lhs, auto rhs) {
const bool leftEven = lhs % 2 == 0;
const bool rightEven = rhs % 2 == 0;
return (leftEven && rightEven) || (!leftEven && !rightEven);
})) {
std::cout << rng << '\n';
}
}
Обратите внимание, что требуются скобки вокруг оператора |=
, так как в противном случае оператор составления (|
) диапазонов будетоценивается в первую очередь, и мы получим приведенный выше код, печатающий отсортированные элементы вектора, полностью игнорируя группирование ( ??? ).
Этот подход okaaaay , но все еще не велик. Я бы предпочел либо иметь group_by
, который мог бы, например, принять значение и вернуть ключ (Java
и C#
подход к обработке группировки), либо в любом случае принять во внимание весь диапазонили, по крайней мере, actions::partition
доступны.
Примечание: я вижу обоснование для views::grouping_by
, работающей только со смежными элементами. Это самый эффективный способ - не нужно ничего хранить, не нужно возвращаться или смотреть дальше. Это нормально, а иногда это лучший инструмент для работы. Но я полагаю, что это создает путаницу, поскольку противоречит интуиции людей, которые работали с подобными API в прошлом.
И, наконец, повторяю вопрос - есть ли более краткий способ сделать то, что я хочу, основываясь на примерахи желаемые подходы, которые я предложил?
1 Я не могу найти его на cppreference , но я думаю, что где-то получил подтверждение, что он находится. Исправитьпожалуйста, если я ошибаюсь.