如何在 std::transform 中的向量中使用其他值?

How do I use other values in a vector in a std::transform?

给定下面的代码,它只是将向量的每个元素加 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 或其他算法来完成?

您可以添加 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 起支持使用初始化程序进行捕获。

Instead of just adding 1 to each value I want to add the value of the previous element.

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

@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;
});

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

但是,对于手头的工作,我个人会采取稍微不同的方法。您正在使用的 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(并保留其余的代码完好无损)。