【问题标题】:How do I use other values in a vector in a std::transform?如何在 std::transform 的向量中使用其他值?
【发布时间】:2021-01-19 07:49:12
【问题描述】:

给定下面的代码,它只是将向量的每个元素加 1:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
    // Create vector 1 - print it out
    std::vector<int> v1 {1,2,3,4,5,6,7,8,9};
    for (auto val : v1)
    {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // Create vector 2 by a transform of vector 1 + 1
    std::vector<int> v2(v1.size());
    std::transform(v1.begin(), v1.end(), v2.begin(),
        [](int val){ return val + 1; });

    // Print out vector 2
    for (auto val : v2)
    {
        std::cout << val << " ";
    }
    std::cout << std::endl;        
}


我想添加前一个元素的值,而不是只为每个值加 1,以便:

v[0] = v[0] (no previous value)
v[1] = v[1] + v[0]
v[2] = v[2] + v[1]
 etc...

所以我最终得到:

1     = 1
2 + 1 = 3
3 + 2 = 5
4 + 3 = 7
etc...

这是一个愚蠢的例子 - 它不是数学难题或类似的东西,我只是想弄清楚我是否可以在我的向量中使用其他值,而不是传递给 lambda 函数的值?

我在 lambda 中丢失了两位数据:

    std::transform(v1.begin(), v1.end(), v2.begin(), [](int val){
            // What is the index of val?
            // What is the size of the vector v1?
            // How would I express this:
            if (INDEX_OF(val) > 0)
                return val + INDEX_OF(val - 1);
            else
                return val;
        });

我可以通过捕获传递 v1 并使用它来进行一些计算,但由于我不知道索引,所以不是我想要的。

    std::transform(v1.begin(), v1.end(), v2.begin(),
        [&v1](int val){ return val + v1.size() - v1[0]; });

也许转换不是这项工作的工具,我需要诉诸迭代器循环?:

    std::vector<int> v2(v1.size());
    auto it2 = v2.begin();
    for (auto it1 = v1.begin(); it1 != v1.end(); it1++, it2++)
    {
        std::distance(v1.begin(), it1) == 0 ? *it2 = *it1 : *it2 = *it1 + *(it1 - 1);
    }

这可行,但我的问题是要知道它是否可以使用 std::transform 或其他算法来完成?

【问题讨论】:

    标签: c++ algorithm lambda stl


    【解决方案1】:

    @songyuanyao 回答了这个问题。这是一个类似的解决方案,展示了如何为容器中的先前值添加通用内存量。

    #include <memory>
    
    template<typename It1, typename It2>
    void slide_transform(It1 first, It1 last, It2 write, size_t n = 1) {
    
        // a memory bank to store the previous values
        auto mem = std::make_unique<typename std::iterator_traits<It1>::value_type[]>(n);
    
        size_t rw = 0; // current position in memory
    
        std::transform(first, last, write,
            [&](const auto& val) {
                auto nv = val + mem[rw];
                mem[rw] = val;
                rw = (rw + 1) % n;
                return nv; 
            }
        );
    }
    
    // ...
    
    // change 1 to a bigger value below to add earlier elements
    slide_transform(v1.begin(), v1.end(), v2.begin(), 1);
    

    为了真正有用,可能希望能够提供一个用户定义的函子,将前一个值作为参数,在这种情况下,我会跳过使用 std::tranform 并执行以下操作:

    #include <memory>
    
    template<typename It1, typename It2, typename Func>
    void slide_transform(It1 first, It1 last, It2 write, size_t n, Func func) {
        auto mem = std::make_unique<typename std::iterator_traits<It1>::value_type[]>(n);
        size_t rw = 0;
        for(;first != last; std::advance(first, 1)) {
            auto copy = *first;
            *write = func(mem[rw], *first);
            std::advance(write, 1);
            mem[rw] = copy;
            rw = (rw + 1) % n;
        }
    }
    // ...
    
    slide_transform(v1.begin(), v1.end(), v2.begin(), 1, [](auto prev_val, auto val) {
        return prev_val + val;
    });
    

    【讨论】:

    • 感谢这篇文章。与简单的 3-4 行迭代器循环相比,这感觉太复杂了!但这里的概念很有趣! - 我还假设这仅适用于具有连续内存的容器? +1 努力:)
    • @code_fodder 谢谢!是的,它适用于更通用的情况,所以它变得有点复杂。它适用于任何容器,因为它只使用迭代器。
    【解决方案2】:

    您已经得到了几个答案,指出了使用捕获来临时存储下一次迭代的先前值的能力。

    但是,对于手头的工作,我个人会采取稍微不同的方法。您正在使用的 std::transform 的重载采用一个输入范围,对每个元素应用一个操作,并将每个结果推送到一个输出范围。

    但是,还有另一个重载需要两个输入范围,应用一个操作来组合它们,然后将结果推送到输出。这更符合您的要求 - 只是在您的特定情况下,两个输入范围相互重叠。

    这确实很难处理您想要完整复制的第一个元素。我们可以创建一个产生 0 的幻像第一个元素的代理,但对于第一次尝试,可能更容易复制第一个元素,然后使用算法处理剩余的项目。

    如果你真的只关心你提出的问题,那么捕获一个引用并使用它来临时存储从一次调用到下一次调用的值可能是更明显的选择。

    这样做的好处是,当(例如)您想要将每个元素添加到之前的 4 或 5 个元素而不是前一个元素时,它很容易应用。对于更一般的问题形式,我可能会按照这个一般顺序编写代码:

    #include <vector>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    
    int main() { 
        std::vector<int> v1 { 1, 2, 3, 4, 5, 6, 7, 8, 9};
        std::vector<int> v2;
    
        int offset = 1;
    
        // copy the first N elements intact
        std::copy(v1.begin(), v1.begin() + offset, std::back_inserter(v2));
    
        // each remaining element gets added to the one N previous:
        std::transform(v1.begin()+offset, v1.end(), 
            v1.begin(), 
            std::back_inserter(v2), 
            [](auto a, auto b){ return a + b; });
    
        // show the result:
        std::copy(v2.begin(), v2.end(), std::ostream_iterator<int>(std::cout, "\t"));
        std::cout << "\n";
    }
    

    就目前而言,这会将offset 设置为1,这与您提出的问题相匹配。但是,例如,如果您希望复制前三个项目并将其余项目分别添加到输入中的元素 3 之前,您可以将 offset 更改为 3(并保持其余代码不变)。

    【讨论】:

    • 这是一个聪明的解决方案 (+1) - 正如你所说,这适用于我发布的具体示例,现在也有一个偏移量 - 这是很酷的部分 - 但它不允许我们访问items infront - 我想我们可以为负偏移提出类似的解决方案,但这开始变得非常复杂 - 我认为在这种情况下我们真的可以击败简单的迭代器循环:)
    • @code_fodder:负偏移与正偏移并没有多大意义。如果您想要其他方向的项目,您只需在 lambda 表达式中反转使用它们的方式。
    【解决方案3】:

    您可以添加 lambda 捕获来存储前一个元素的值。

    例如

    std::vector<int> v2(v1.size());
    std::transform(v1.begin(), v1.end(), v2.begin(),
        [pv = 0](int val) mutable { int nv = pv + val; pv = val; return nv; });
    

    LIVE

    PS:自 C++14 起支持使用初始化程序捕获。

    【讨论】:

    • 真是太感谢了 - 几个问题:我似乎找不到对 pv 的引用 - 那是什么?为什么它是只读的(所以你必须使用可变的)? - 还有其他方法来索引值吗? - 你可以对下一个值(即索引 + 1)做同样的事情吗? - 我认为不是...
    • @code_fodder 我想也不是。我们无法在 std::transform 这样的算法中获取索引(和下一个值),我们只能获取 lambda 中的元素。我们必须添加 mutable 否则 lambda 的 operator() 将是 const 限定的,我们无法修改捕获的变量。
    • 啊,我明白了。我仍然对pv 感到有些困惑——它是从哪里来的? - 是某种自动扣除的临时费用吗? - 我正在尝试查找(此处为en.cppreference.com/w/cpp/language/lambda#Lambda_capture),但我找不到允许这种行为的部分! - 无论如何,现在我有答案了
    • @code_fodder 它只是在 lambda 闭包类型中声明了一个名为 pv 的数据成员,然后我们可以在 lambda 的主体中使用它,即在 operator() 中。在您链接的页面中,您可以参考从 带有初始化程序的捕获开始的段落,就好像它声明并显式捕获一个变量....
    • 啊,我几乎猜对了 - 好技巧,谢谢 :)
    【解决方案4】:

    我想添加前一个元素的值,而不是只为每个值加 1。

    您准确地描述了标准库中的partial_sum 函数。您也可以将用户定义的函数作为参数传递。要了解更多信息,请点击here

    【讨论】:

    • 不,OP 只想一次添加两个相邻的元素。也就是说,对于输入std::vector&lt;int&gt; v1 {1,2,3,4,5,6,7,8,9},结果应该是{3,5,7,9,11,13,15,17},而不是{1,3,6,10,15,21,28,36,45}
    • @EloyPérezTorres 这是一个有趣的算法,谢谢 :) 虽然我真的只是想知道我是否可以访问向量中的其他元素
    猜你喜欢
    • 2022-06-22
    • 2013-02-07
    • 2016-03-01
    • 2014-02-27
    • 2011-08-08
    • 2021-09-22
    • 1970-01-01
    • 2020-03-22
    相关资源
    最近更新 更多