C ++ 20系列是否支持按功能分组?

问题描述

有时,根据对象的成员函数之一(getter或某种计算)的值对对象进行分组/分区非常有用。

C ++ 20范围是否启用类似的功能

std::vector<Person> {{.Age=23,.Name = "Alice"},{.Age=25,.Name = "Bob"},{.Age=23,.Name = "Chad"}};
// group by .Age and put into std::map
std::map<int/*Age is int*/,std::vector<Person>> AgetoPerson = ...;
// 23 -> Person{23,Alice},Person{23,Chad}
// 25 -> Person{25,Bob}

注1:有一个古老的question,其中可接受的答案是仅使用raw for循环

注2:range-v3具有这种令人困惑的group_by算法,这对我的任务似乎毫无用处:

给出一个源范围和一个二进制谓词,返回一个范围范围,其中每个范围都包含源中的连续元素 满足以下条件的范围:对于 与第一个元素分开的范围 传递给二进制谓词,结果为true。在本质上, views :: group_by将连续元素与二进制文件一起分组 谓词。

解决方法

使用ranges-v3时,可以结合使用transformto来实现此目的:


#include <range/v3/view/transform.hpp>
#include <range/v3/range/conversion.hpp>
#include <map>
#include <vector>

std::vector<Person> persons{
{.Age=23,.Name = "Alice"},{.Age=25,.Name = "Bob"},{.Age=23,.Name = "Chad"}};

// group by .Age and put into std::map
auto AgeToPerson = persons
    | ranges::view::transform([](const auto& person)
        {
            return std::pair{person.Age,person};
        })
    | ranges::to<std::map<int,Person>>();

请记住,采用这种方法,每个年龄段只能得到一个Person,这就是为什么您可能想使用std::multimap而不是std::map

的原因 ,

在以下名称组下,语言实际上提供了三种不同类型的功能:

  1. 采用二进制谓词((T,T) -> bool)并将该谓词评估为true的连续元素分组(例如Haskell,Elixir,D,范围v3类)
  2. 采用一元函数(T -> U),并使用相同的“键”对连续元素进行分组,并生成一系列U,[T]对(例如Rust,Python,D,F#)
  3. 采用一元函数(T -> U)并返回映射U: [T]的字典(例如Clojure,Kotlin,Scala)。

前两个需要连续元素-这意味着您需要按键排序。最后一个没有,因为您仍然在生产容器。即使没有排序,您也可以从第二个开始生成第三个版本,尽管那仍然需要循环

但是如上所述,range-v3仅提供第1个,而C ++ 20中甚至没有。因此,您需要编写自己的东西。在这种情况下,循环可能是最好的:

template <range R,indirectly_unary_invocable<iterator_t<R>> F>
    /* other requirements such that you can form a map */
auto group_by_into_map(R&& range,F&& f)
{
    unordered_map<
        decay_t<indirect_result_t<F&,iterator_t<R>>>,// result of unary function
        vector<range_value_t<R>>                       // range-as-vector
    > map;

    for (auto&& e : range) {
        map[std::invoke(f,e)].push_back(e);
    }

    return map;
}

某种效果。这样可以:

group_by_into_map(people,&Person::Age);

,除非您可以使用std::unordered_multimap。人们会使用吗?这是一个奇怪的容器。但是假设您是,那么这要容易得多。您可以编写自己的适配器:

template <typename F> // NB: must be unconstrained
auto group_by_into_map(F&& f) {
    return views::transform([=](auto&& e){ return std::pair(std::invoke(f,e),e); })
         | ranges::to<std::unordered_multimap>();
        
}

允许:

people | group_by_into_map(&Person::Age);

但这会给您unordered_multimap<int,Person>而不是unordered_map<int,vector<Person>>