В std::ranges
C++20
мы можем ожидать получения views::group_by
1. Это может быть очень удобно, но во время игры я обнаружил проблему. Из руководства Эрика Ниблера мы можем прочитать, что "< em>По сути, 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
объединены вместе, потому что они оба и непрерывны. Аналогично (за исключением того, что они четные) 12
и 10
. Но 7
, 5
и 1
создадут отдельную группу - они не будут объединены с 3
и 9
, а этого я бы не хотел и не ожидал.
Что мы могли бы, конечно, сделать, так это partition
вектор ints
упорядочить элементы так, чтобы четы и шансы образовали две группы. Проблема в том, что в диапазонах нет views::
partition
. Это оставляет мне два варианта, ни один из которых мне особенно не нравится:
1. std
ranges::partition перед просмотром вектора:
std
Вызов:
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';
}
}
Обратите внимание, что круглые скобки вокруг оператора |=
обязательны, так как в противном случае оператор составления (|
) диапазонов будет оцениваться первым, и мы получим приведенный выше код, печатающий отсортированные элементы вектора, полностью игнорируя группировку (? ??).
Этот подход неплохой, но все же не лучший. Я бы предпочел либо иметь group_by
, который мог бы, например, принимать значение и возвращать ключ (подход Java
и C#
к обработке группировки), либо каким-либо образом учитывать весь диапазон, или, по крайней мере, иметь actions::partition
доступным.
Примечание: я вижу смысл в том, что views::grouping_by
работает только с непрерывными элементами. Это самый эффективный способ - не нужно ничего сохранять, не нужно возвращаться назад или смотреть дальше. Это нормально, а иногда это лучший инструмент для работы. Но я считаю, что это создает путаницу, будучи нелогичным для людей, которые работали с подобными API в прошлом.
И, наконец, повторю вопрос - есть ли более краткий способ сделать то, что я хочу, на основе предложенных мной примеров и желаемых подходов?
1 Я не могу найти его на cppreference, но Мне кажется, я где-то видел подтверждение того, что он есть. Поправьте меня, пожалуйста, если я ошибаюсь.
std::views::filter()
? - person Deduplicator   schedule 31.10.2019std::not_fn()
для тех, кто этого не делает. - person Deduplicator   schedule 31.10.2019return !(lhs % 2) == !(rhs % 2);
? - person Deduplicator   schedule 31.10.2019lhs % 2 == rhs % 2
короче ;› - person Fureeish   schedule 31.10.2019-1
и+1
. Используя мое предложение или ваше чрезмерное в посте, они находятся в одной группе. Но если взять их% 2
, они все равно-1
и+1
. - person Deduplicator   schedule 31.10.2019