如何使用算法填充向量的向量

5

我有

typedef std::vector<int> IVec;
typedef std::vector<IVec> IMat;

我想知道如何使用std算法来填充IMat,也就是如何用更少的代码完成以下操作(所有的IVec都具有相同的大小)?

void fill(IMat& mat){
    for (int i=0;i<mat.size();i++){
        for (int j=0;j<mat[i].size();j++){
            mat[i][j] = i*j;
        }
    }
}

PS:已经有一种方法可以用常量填充矩阵,最好使用C++11之前的算法。


2
由于值取决于索引,因此请继续使用循环。 - deviantfan
1
你需要初始化空向量,还是填充已经不为空的向量的值?此外,你想用基于索引的值(i*j)来填充它们,还是任何值(例如 0)都可以? - SingerOfTheFall
为了好玩,我尝试使用嵌套的std::transform而不是显式循环或for_each来完成它。http://coliru.stacked-crooked.com/a/96f803182ad4abad从技术上讲,您可以将所有内容放在一行上以使其“代码更少”,但为什么要这样做呢? OP中的简单循环更加清晰。 - AndyG
@tobi303 我同意简单循环在这里是最好的,但每个lambda都可以被一个实现了operator()的类对象所替代。 - deviantfan
@deviantfan 是的,我知道,但这通常会导致代码更多而不是更少。据我所知,这就是引入lambda表达式的原因。 - 463035818_is_not_a_number
显示剩余2条评论
3个回答

5
最好的解决方案是您已经实现的那个。它利用使用/j作为偏移量和输入来计算算法。
标准算法将不得不使用迭代器来处理元素,并维护计数器。这种数据镜像是问题的明显标志。但是,即使您想要花哨一点,也可以在一行上完成:
for_each(mat.begin(), mat.end(), [&](auto& i) { static auto row = 0; auto column = 0; generate(i.begin(), i.end(), [&]() { return row * column++; }); ++row; });

但是,正如所述,仅因为可以做到并不意味着应该这样做。最好的方法是使用for循环。即使在一行上完成也是可能的,如果那是你的风格:

for(auto i = 0U;i < mat.size();i++) for(auto j = 0U;j < mat[i].size();j++) mat[i][j] = i*j;

顺便提一下,我的标准算法在Clang 3.7.0, gcc 5.1, 和Visual Studio 2015上都能正常运行。但是{{link3:以前我使用的是transform而不是generate}}。并且似乎在lambda作用域static变量的捕获中有一些实现问题在gcc 5.1和Visual Studio 2015中


如果可以的话,将东西放在一行上非常符合我的风格,但是如果我必须做出决定,省略for循环体周围的{}将是被禁止的 ;) - 463035818_is_not_a_number
@tobi303 因为换行是免费的,所以我在我的个人代码中大量使用它们。(连同花括号一起。)我认为这使得代码更易读。但归根结底,在这种情况下最重要的决定不是是否在一行上完成,而是在for循环中完成。 - Jonathan Mee
2
仅凭这一句话就值得点赞:“但是正如所说,仅仅因为某件事情可能被做到,并不意味着它应该被做到。” - Richard Hodges

3

我想进一步评论Jonathan的出色回答。

暂时忽略c++11语法,假设我们已经编写了一些支持类(现在无关紧要)。

我们可以想象出如下代码:

auto main() -> int
{
    // define a matrix (vector of vectors)
    IMat mat;

    // resize it through some previously defined function
    resize(mat, 10, 10);

    // get an object that is a pseudo-container representing its extent
    auto extent = extent_of(mat);

    // generate values in the pseudo-container which forwards to the matrix
    std::generate(extent.begin(), 
                  extent.end(), 
                  [](auto pxy) { pxy.set_value(pxy.x * pxy.y); });

    // or even

    for (auto pxy : extent_of(mat)) {
        pxy.set_value(product(pxy.coordinates()));
    }

    return 0;
}

100行的支持代码后(可迭代容器及其代理并不是微不足道的),这段代码可以编译并运行。
虽然它无疑非常聪明,但存在一些问题:
- 还需要额外的100行代码。 - 我认为,这段代码实际上比你的代码更加难以表达。也就是说,你的代码立即清楚地说明了它在做什么。而我的代码则需要进行某些假设或推理额外的100行代码。 - 我的代码需要比你的代码更多的维护(和文档)工作。 有时候,少即是多。

3

我不确定这种方法是否比使用双重循环更好,但在C++11中使用STL的一种可能方法是使用两个for_each,如下所示:

int i(0);
std::for_each(mat.begin(), mat.end(),
[&i](IVec &ivec){int j(0); std::for_each(ivec.begin(), ivec.end(), 
                           [&i,&j](auto &k){k = i*j++;}); ++i;});

LIVE DEMO


2
我非常确定 generate 的顺序不能保证 => 根据实现的不同,你可能填入了错误的值。 - deviantfan
@deviantfan 不知道这个,那真是惊喜呢。 - 101010
1
@deviantfan 你能提供任何证据来支持吗?http://en.cppreference.com/w/cpp/algorithm/generate#Possible_implementation 将其实现为:while (first != last) *first++ = g();。我一直认为所有迭代器算法都是顺序的。如果不是这种情况,可能会出现很多问题,例如使用lambda表达式。 - Jonathan Mee
1
@JonathanMee 在标准没有规定算法顺序的任何地方,您应该假设实现可以利用这一点来进行并行处理。 - 101010
1
for_each是有序的(从头到尾,参见§25.2.4.2)。为什么无序的东西对lambda函数是个问题? - deviantfan
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接