STL算法的可组合性

时间:2011-07-19 06:28:14

标签: c++ stl stl-algorithm

STL算法在C ++中非常有用。但有一件让我烦恼的事情是他们似乎缺乏可组合性。

例如,假设我有一个vector<pair<int, int>>,并希望将其转换为仅包含该对vector<int>成员的second。这很简单:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;

std::transform(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return p.second; });

或者我想仅为那些vector成员是偶数的对过滤first。也很简单:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> result;

std::copy_if(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return (p.first % 2) == 0; });

但如果我想同时做两件事怎么办?没有transform_if算法,同时使用transformcopy_if似乎需要分配一个临时vector来保存中间结果:

std::vector<std::pair<int, int>> values = GetValues();
std::vector<std::pair<int, int>> temp;
std::vector<int> result;

std::copy_if(values.begin(), values.end(), std::back_inserter(temp),
    [] (std::pair<int, int> p) { return (p.first % 2) == 0; });

std::transform(values.begin(), values.end(), std::back_inserter(result),
    [] (std::pair<int, int> p) { return p.second; });

这对我来说似乎相当浪费。我能想到避免临时向量的唯一方法是放弃transformcopy_if并简单地使用for_each(或者使用常规for循环,以适合您的想法为准):

std::vector<std::pair<int, int>> values = GetValues();
std::vector<int> result;

std::for_each(values.begin(), values.end(),
    [&result] (std::pair<int, int> p) 
        { if( (p.first % 2) == 0 ) result.push_back(p.second); });

我在这里遗漏了什么吗?有没有一种很好的方法可以将两个现有的STL算法组合成一个新的算法而不需要临时存储?

5 个答案:

答案 0 :(得分:24)

你是对的。您可以使用Boost.Range adaptors来实现合成。

答案 1 :(得分:10)

我认为这个问题不幸是结构性的

  1. C ++使用两个迭代器来表示序列
  2. C ++函数是单值的
  3. 因此您无法链接它们,因为函数无法返回“序列”。

    一个选项可能是使用单个对象序列(like the range approach from boost)。通过这种方式,您可以将一个处理的结果组合为另一个处理的输入...(一个对象 - >一个对象)。

    在标准C ++库中,处理是(两个对象 - >一个对象),很明显,如果不命名临时对象,就不能链接它。

答案 2 :(得分:8)

早在2000年,问题就已经出现了。 Gary Powell和Martin Weiser提出了一个“视图”概念,并创造了“查看模板库”的名称。它没有起飞,但这个想法是有道理的。 “视图”适配器基本上应用了即时转换。例如,它可以调整value_type

现在我们有了C ++ 0x,这个概念可能应该被重新解读。自2000年以来,我们在通用编程方面取得了一些进展。

例如,我们使用vector<pair<int, int>>vector<int>示例。这可能很简单:

std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, [](std::pair<int, int> p) { return p.first }); 
std::vector<int> result(view.begin(), view.end());

或者,使用boost::bind技术,甚至更简单:

std::vector<std::pair<int, int>> values = GetValues();
vtl2::view v (values, &std::pair<int, int>::first); 
std::vector<int> result(view.begin(), view.end());

答案 3 :(得分:1)

由于 C++20,您可以将 std::ranges::copystd::views::filter 中的范围适配器 std::views::valuesRanges library 一起使用,如下所示:

int main() {
    std::vector<std::pair<int, int>> values = { {1,2}, {4,5}, {6,7}, {9,10} };
    std::vector<int> result;

    auto even = [](const auto& p) { return (p.first % 2) == 0; };
    std::ranges::copy(values | std::views::filter(even) | std::views::values,
                      std::back_inserter(result));

    for (int i : result)
        std::cout << i << std::endl;

    return 0;
}

输出:

<块引用>

5
7

在上面的解决方案中,没有为中间结果创建临时向量,因为视图适配器创建了不包含元素的范围。这些范围只是输入向量的视图,但具有自定义的迭代行为。

Code on Wandbox

答案 4 :(得分:0)

不确定是否仍处于活动状态,但是... 一个新的light wait header only lib可以执行您描述的操作。 Doc讨论了惰性评估和可组合生成器。

文档摘要:

  • 从文件“ test.txt”中读取最多10个整数。
  • 过滤偶数,将它们平方并求和。
    int total = lz::read<int>(ifstream("test.txt")) | lz::limit(10) |
                lz::filter([](int i) { return i % 2 == 0; }) |
                lz::map([](int i) { return i *  i; }) | lz::sum();

您可以将该行分成多个表达式。

    auto numbers = lz::read<int>(ifstream("test.txt")) | lz::limit(10);
    auto evenFilter = numbers | lz::filter([](int i) { return i % 2 == 0; });
    auto squares = evenFilter | lz::map([](int i) { return i *  i; });
    int total = squares | lz::sum();
  • 即使此表达式被分成多个变量分配,它的效率也丝毫没有降低。
  • 每个中间变量都简单 描述要执行的代码单元。全部堆叠在一起。

https://github.com/SaadAttieh/lazyCode