使用std::fill函数填充向量以增加数字

120
我想使用std::fill填充一个vector<int>,但这个向量不应该只包含一个值,而是在填充时依次递增。
我尝试通过将函数的第三个参数迭代一次来实现这一点,但这只会给我一个填充了1或2的向量(取决于++操作符的位置)。
示例:
vector<int> ivec;
int i = 0;
std::fill(ivec.begin(), ivec.end(), i++); // elements are set to 1
std::fill(ivec.begin(), ivec.end(), ++i); // elements are set to 2

25
使用std::iota代替std::fill(假设您的编译器足够新支持它)。 - Jerry Coffin
1
不幸的是,这似乎是新标准的一部分(我不能使用)。我看到BOOST库有这样一个函数,但它不接受向量(http://www.boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/algorithms/new/iota.html),而是一些自定义类型。难道没有其他选择吗? - BlackMamba
2
如果您无法使用C++11/Boost,请使用Liran的代码。并不是每个操作都必须在一行上,也不是C源代码文件中字符可用性全球短缺的要求 :-) - paxdiablo
这不是因为短缺,而是因为性能问题。但是,如果没有残酷的黑客方式实现这一点,我将使用Liran提供的解决方案。 - BlackMamba
@user1612880 你尝试过使用std :: vector吗?boost版本是一个函数模板,第一个参数的“类型名称”指定了一个概念。很难说,因为我只能找到非常形式化的规范,没有简单的描述,但我认为std :: vector符合该概念。 - James Kanze
是的,boost::range::iotastd::vector 完美配合。是的,std::vector 模拟了 ForwardRange(确切地说是 RandomAccessRange)。 - Casey
17个回答

176
最好像这样使用std::iota
#include <vector>
#include <numeric>
// ...
std::vector<int> v(100) ; // vector with 100 ints.
std::iota (std::begin(v), std::end(v), 0); // Fill with 0, 1, ..., 99.

那么,如果你没有任何支持(在我工作的地方仍然是一个真正的问题),可以像这样使用std::generate
#include <vector>
#include <algorithm>
// ...
struct IncGenerator {
    int current_;
    IncGenerator (int start) : current_(start) {}
    int operator() () { return current_++; }
};

// ...

std::vector<int> v(100) ; // vector with 100 ints.
IncGenerator g (0);
std::generate( v.begin(), v.end(), g); // Fill with the result of calling g() repeatedly.

9
“iota” 究竟代表什么意思呢?看起来有人打错了同样清晰的“itoa”。请问需要翻译什么内容? - Luke Usherwood
19
它并不代表任何含义,它是希腊字母 i 的等价物。这是在 APL(一种与 STL 相关的数组语言)中用于类似功能的名称。APL 激发了 Stepanov 在 STL 中的很多思想。 - BoBTFish
2
啊哈,谢谢!好的,所以这是一个单词而不是首字母缩写;现在我可能更容易记住了。CPP参考文献中提到了“起始值的增量”(注意略微相似之处),所以我脑海中想到了首字母缩写。(通过谷歌搜索,希腊连接并不立即显而易见。)感谢您提供的历史参考。 - Luke Usherwood

61

您应该使用<numeric> 中定义的std::iota算法:

  std::vector<int> ivec(100);
  std::iota(ivec.begin(), ivec.end(), 0); // ivec will become: [0..99]

std::fill将给定的固定值分配给给定范围[n1,n2)中的元素。而std::iota使用初始值填充给定范围[n1,n2),然后使用++value逐步增加值。您也可以使用std::generate作为替代方法。

请注意,std::iota是C++11 STL算法。但是许多现代编译器都支持它,例如GCC、Clang和VS2012:http://msdn.microsoft.com/en-us/library/vstudio/jj651033.aspx

P.S. 这个函数是以编程语言APL中的整数函数命名的,并表示希腊字母iota。我猜想最初在APL中选择这个奇怪的名称,是因为它类似于一个“整数”(即使在数学中iota被广泛用于表示复数的虚部)。


1
你可能想添加 std::iota,它来自于C++11。 - hetepeperfan
但是大多数地方都没有使用最新版本的编译器,也无法使用C++11。 - James Kanze
1
“iota”在STL中出现已经超过15年了,因此一些编译器早在C++11之前就一直支持它。 - Jonathan Wakely
2
那个向量的大小将为0。您需要向容器添加一个大小: std::vector<int> ivec(100); std::iota(ivec.begin(), ivec.end(), 0); - Llopeth

15

即使在 C++11 中,我的首选仍然是 boost::counting_iterator

std::vector<int> ivec( boost::counting_iterator<int>( 0 ),
                       boost::counting_iterator<int>( n ) );

或者如果向量已经被构建:
std::copy( boost::counting_iterator<int>( 0 ),
           boost::counting_iterator<int>( ivec.size() ),
           ivec.begin() );

如果无法使用Boost库:可以选择使用std::generate(如其他答案所建议的),或者自己实现counting_iterator,如果需要在不同的地方使用它。 (使用Boost库,你可以使用transform_iteratorcounting_iterator创建出各种有趣的序列。如果没有Boost库,则可以手写很多内容,可以作为std::generate的生成器对象类型,也可以作为可以插入手写计数迭代器中的东西。)

在第一段代码中,调用了std::vector的哪个构造函数?必须是范围构造函数,但是boost::counting_iterator是否隐式转换为输入迭代器 - CinCout

11

我看到了使用std :: generate的答案,但你也可以通过在lambda内部使用静态变量来“改进”,而不是在函数外部声明计数器或创建生成器类:

std::vector<int> vec(10);
std::generate(vec.begin(), vec.end(), [] {
    static int i = 0;
    return i++;
});

我觉得这更简洁了一些。


9
在我看来,static在这里的语义是错误的。我会使用广义捕获[i = 0]() mutable,以便清楚地表明变量的作用域限于lambda的特定实例,而不是其生成的类类型。很难想象实践中会有什么区别,这可能意味着设计存在问题,但无论如何,我认为使用成员变量的语义更加优越。此外,这可以使代码更简洁;现在lambda的主体可以是一个单独的语句。 - underscore_d
是的,看起来更好了;我以前从未见过在lambda捕获中初始化变量 :) - brainsandwich
1
这不会生成任何东西。vec是空的。虽然我试图在std中找到一些可以将一系列递增的值生成到一个空容器中的东西。:( - Adrian

7
We可以使用算法头文件中存在的generate函数。

代码片段:

#include<bits/stdc++.h>
using namespace std;


int main()
{
    ios::sync_with_stdio(false);

    vector<int>v(10);

    int n=0;

    generate(v.begin(), v.end(), [&n] { return n++;});

    for(auto item : v)
    {
      cout<<item<<" ";
    }
    cout<<endl;

    return 0;
}

这是一个非常优雅的答案,可能是一般情况下最简洁的。 - Owl
2
在我看来,通过广义捕获[n = 0]()mutable使n成为lambda的成员,这样它就不会污染周围的作用域,这种方法更优越。 - underscore_d
我正在使用这个。对于C++初学者来说足够好,但在高级情况下可能不太有用。如果能使用lambda表达式,则是一个很好的解决方案。 - Abinash Dash

6

std::iota仅适用于序列n, n+1, n+2, ...

但是如果你想使用通用序列f(0), f(1), f(2)等来填充数组呢?通常情况下,我们可以避免使用状态跟踪生成器。例如,

int a[7];
auto f = [](int x) { return x*x; };
transform(a, a+7, a, [a, f](int &x) {return f(&x - a);});

将会生成一个平方数序列。
0 1 4 9 16 25 36

然而,这个技巧在其他容器上不起作用。

如果你被困在C++98中,你可以做一些可怕的事情,比如:

int f(int &x) { int y = (int) (long) &x / sizeof(int); return y*y; }

然后

int a[7];
transform((int *) 0, ((int *) 0) + 7, a, f);

但我不建议这样做。 :)

1
OP 无法使用 C++11(没有 lambda)。 - yizzlez

5

如果您不想使用C++11功能,可以使用std::generate函数:

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

struct Generator {
    Generator() : m_value( 0 ) { }
    int operator()() { return m_value++; }
    int m_value;
};

int main()
{
    std::vector<int> ivec( 10 );

    std::generate( ivec.begin(), ivec.end(), Generator() );

    std::vector<int>::const_iterator it, end = ivec.end();
    for ( it = ivec.begin(); it != end; ++it ) {
        std::cout << *it << std::endl;
    }
}

这个程序打印从0到9的数字。

3
如果你有lambda表达式的话,肯定也会有std::iota,不是吗? - BoBTFish
1
@bitmask 但那需要 C++11 :) - juanchopanza
2
相当不直观和不流畅的是 itend 定义在循环外部。这样做的原因是什么? - Christian Rau
1
@ChristianRau:这是一种避免重复调用std::vector<T>::end()而不必多次输入迭代器类型名称std::vector<int>::const_iterator的方法,只是一种习惯。在我的实际代码中,我也是这样做的,通常并不重要,因为函数非常短,itend的作用域非常小。现在,你可以说调用end()非常便宜,我应该只是输入for ( std::vector<int>::const_iterator it = ivec.begin(); it != ivec.end(); ++it ),但那一行太长了。 :-) - Frerich Raabe
2
@ChristianRau:说实话,在实践中,我使用正在编辑的文件中代码所使用的任何风格,即我更重视一致性,而不是我们迄今为止提到的任何优缺点。 - Frerich Raabe
显示剩余6条评论

4

还有一种选择 - 不使用 iota。 可以使用 for_each + lambda表达式:

vector<int> ivec(10); // the vector of size 10
int i = 0;            // incrementor
for_each(ivec.begin(), ivec.end(), [&](int& item) { ++i; item += i;});

为什么它能够工作的两个重要因素:

  1. Lambda捕获外部作用域[&],这意味着i将在表达式内可用
  2. item以引用的方式传递,因此它在向量中是可变的

2

说到boost:

auto ivec = boost::copy_range<std::vector<int>>(boost::irange(5, 10));

2

这也可以工作

j=0;
for(std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it){
    *it = j++;
}

4
当然,手动使用迭代器进行操作是可行的,但如果提问者想这样做,他们就不会询问有关stdlib算法的问题了,对吧? - underscore_d

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