探索 cartesian_product:更深入理解范围库

一、简介

view::cartesian_product 适配器是range-v3 库一个新的组件。本文主要理解这个组件的功能以及它背后的设计理念,可以更好地掌握range库。虽然可以通过研究 zip 适配器来理解这些内容,但 cartesian_product 是一个全新的适配器,探索它既可以掌握range库,也可以学习新技术,一举两得。

为什么要花时间理解范围库呢?

因为范围是 STL 的未来。STL 是一个强大的工具,可以编写富有表现力的代码,而范围库是一个设计精良的库,它将 STL 的功能推向了更远。简而言之,学习范围库可以了解编写富有表现力的 C++ 代码的趋势。

二、cartesian_product 适配器的动机

cartesian_product 适配器的目的是遍历多个集合元素的所有可能组合。

为了避免引入业务逻辑,本文将使用一些简单的示例,但它在实际应用中非常有用,例如当对象具有版本时。在这种情况下,可能希望生成所有对象在所有可能日期上的组合。

为了说明问题,使用以下三个集合:

  • 一个包含数字的集合:

    std::vector<int> numbers = {3, 5, 12, 2, 7};
    
  • 一个包含字符串表示的常见聚会食物类型的集合:

    std::vector<std::string> dishes = {"pizzas", "beers", "chips"};
    
  • 一个包含字符串表示的聚会地点的集合:

    std::vector<std::string> places = {"London", "Paris", "NYC", "Berlin"};
    

现在,希望对这三个集合元素的所有可能组合执行一个操作,例如打印一个句子。
在这里插入图片描述

三、将行为封装到算法中

编写一个通用的函数,可以将一个函数应用于多个集合元素的所有可能组合。为了专注于算法的职责,这里省略了可变参数的内容:

template<typename Collection1, typename Collection2, typename Collection3, typename Function>
void cartesian_product(Collection1&& collection1, Collection2&& collection2, Collection3&& collection3, Function func)
{
    for (auto& element1 : collection1)
        for (auto& element2 : collection2)
            for (auto& element3 : collection3)
                func(element1, element2, element3);
}

这个函数可以完成任务。调用:

cartesian_product(numbers, dishes, places,
    [](int number, std::string const& dish, std::string const& place)
    { std::cout << "I took " << number << ' ' << dish << " in " << place << ".\n";});

四、算法的局限性

虽然看起来不错,但如果稍微改变一下需求,上面的代码就会失效。假设不再希望函数直接写入控制台,而是希望将各种组合输出到一个字符串容器中。

在这种情况下,无法使用上面的实现,因为它没有返回值。

实际上,上面的算法类似于 std::for_each 的所有可能组合版本,因为它遍历所有组合并应用一个函数。真正需要的是类似于 std::transform 的东西。

难道要重新编写一个新的 cartesian_product 函数,它接受一个输出集合和一个函数,就像 std::transform 一样吗?这感觉不对劲,不是吗?更希望将迭代职责从算法中分离出来。而 cartesian_product 适配器正是为此而生的。

cartesian_product 适配器在多个集合上构建一个视图,将其表示为一个包含所有可能组合的元组的范围。然后,函数需要接受一个包含其参数的元组。需要注意的是,最好直接获取参数,而不是通过元组。

以下是一个满足将句子输出到字符串容器需求的示例:

std::string meetupRecap(std::tuple<int, std::string, std::string> const& args)
{
    int number = std::get<0>(args);
    std::string const& dish = std::get<1>(args);
    std::string const& place = std::get<2>(args);

    std::ostringstream result;
    result << "I took " << number << ' ' << dish << " in " << place << '.';
    return result.str();
}

std::vector<std::string> results;
transform(ranges::view::cartesian_product(numbers, dishes, places), std::back_inserter(results), meetupRecap);

同一个适配器也可以用于将输出写入控制台,而无需编写特定的算法:

void meetupRecapToConsole(std::tuple<int, std::string, std::string> const& args)
{
    int number = std::get<0>(args);
    std::string const& dish = std::get<1>(args);
    std::string const& place = std::get<2>(args);

    std::cout << "I took " << number << ' ' << dish << " in " << place << ".\n";
}

for_each(ranges::view::cartesian_product(numbers, dishes, places), meetupRecapToConsole);

这个适配器有效地承担了生成所有可能元素组合的职责,从而能够重用常规算法,例如 for_eachtransform

五、总结

cartesian_product 适配器是范围库中一个强大的工具,它可以简化生成多个集合元素所有可能组合的过程。通过使用 cartesian_product 适配器,我们可以避免编写复杂的循环嵌套,从而提高代码的可读性和可维护性。

此外,cartesian_product 适配器还能够与其他范围库组件(如 transformfor_each)无缝衔接,进一步提升代码的灵活性和表达力。

虽然目前 cartesian_product 适配器使用元组来传递参数,但未来可能会引入更简洁的接口,例如直接传递参数而不使用元组。这将进一步简化代码编写,并使 cartesian_product 适配器更加易于使用。

cartesian_product 适配器是范围库中一个值得关注的组件,它可以帮助我们更高效地处理元素组合问题,并为编写更富有表现力的 C++ 代码提供新的思路。

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值