【问题标题】:use std::fill to populate vector with increasing numbers使用 std::fill 以增加数字填充向量
【发布时间】:2013-07-15 16:46:01
【问题描述】:

我想用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

【问题讨论】:

  • 使用std::iota 而不是std::fill(无论如何,假设你的编译器足够新来支持它)。
  • 不幸的是,这似乎是新标准的一部分(我不能使用)。我看到 BOOST 库有这样的功能,但它不接受向量 (boost.org/doc/libs/1_50_0/libs/range/doc/html/range/reference/…) 而是一些自定义类型。没有其他选择吗?
  • user1612880,如果你不能使用C++11/Boost,就使用Liran的代码。 要求每个操作都必须在一行上,也不是 C 源代码文件可用的字符在世界范围内都短缺:-)
  • 这不是因为短缺,而是因为性能。但是,如果没有残忍的 hack 就无法实现这一点,我将使用 Liran 提供的解决方案。
  • @user1612880 你试过std::vector。 boost版本是一个函数模板,第一个参数的“类型名”指定了一个概念。很难说,因为我只能找到一个很形式化的规范,没有简单的描述,但我觉得std::vector符合概念。

标签: c++ stl


【解决方案1】:

最好像这样使用std::iota

std::vector<int> v(100) ; // vector with 100 ints.
std::iota (std::begin(v), std::end(v), 0); // Fill with 0, 1, ..., 99.

也就是说,如果您没有任何c++11 支持(在我工作的地方仍然是一个真正的问题),请像这样使用std::generate

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.

【讨论】:

  • iota 到底代表什么? (看起来有人打错了同样清晰的itoa。)
  • 它不“代表”任何东西,它在希腊语中相当于字母 i。它是 similar function in APL 的名称,而数组语言激发了 Stepanov 的 STL 中的许多想法。
  • 啊哈,谢谢!好的,所以它是一个词而不是首字母缩写词;我现在可能会更幸运地记住这一点。 CPP Reference 谈到了“起始值的增量”(注意略有相似之处),所以我脑子里有了首字母。 (谷歌搜索并没有立即发现希腊的联系。)也感谢您的历史参考。
【解决方案2】:

你应该使用std::iota算法(定义在&lt;numeric&gt;):

  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

附:该函数以编程语言 APL 中的整数函数 命名,表示希腊字母 iota。我推测最初在 APL 中选择了这个奇怪的名称是因为它类似于 “integer”(尽管在数学中 iota 被广泛用于表示复数的虚部)。

【讨论】:

  • 你可能想添加 std::iota is from C++11
  • @AlexanderKaraberov 但是大部分地方都没有使用最新版本的编译器,也不能使用C++11。
  • iota 早在 15 年前就在 STL 中了,所以早在 C++11 之前,一些编译器就一直支持它
  • 该向量的大小为 0。您需要向容器添加大小:std::vector ivec(100); std::iota(ivec.begin(), ivec.end(), 0);
【解决方案3】:

我的第一选择(即使在 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_iterator 的一个counting_iterator 创建所有 各种有趣的序列。没有 Boost,你可以做很多事情 手动的,或者以生成器对象类型的形式 对于std::generate,或者作为你可以插入手中的东西 写计数迭代器。)

【讨论】:

  • 在第一段代码中,调用了std::vector 的哪个构造函数?必须是 range 构造函数,但 boost::counting_iterator 是否可以隐式转换为 InputIterator
【解决方案4】:

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

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

我觉得它更简洁一些

【讨论】:

  • static 这里有错误的语义,IMO。我将使用广义捕获[i = 0]() mutable,以便清楚地看出该变量的作用域是 lambda 的特定实例,而不是其生成的类类型。很难设计出在实践中存在差异的情况,这可能表明设计存在问题,但无论如何我认为使用成员变量的语义更好。另外,它使代码更简洁;现在 lambda 的主体可以是单个语句。
  • 是的,看起来更好;我之前从未见过在 lambda 捕获中初始化的变量 :)
  • 这不会产生任何东西。 vec 为空。虽然我试图在std 中找到一些东西,它会在一个空容器中生成一系列递增的值。 :(
【解决方案5】:

我们可以使用算法头文件中存在的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;
}

【讨论】:

  • 这是一个非常优雅的答案,可能是一般情况下最简洁的答案。
  • IMO 最好通过广义捕获 [n = 0]() mutable 使 n 成为 lambda 的成员,因此它不会污染周围的范围。
  • 我正在使用这个。对于高级情况可能没有用,但对于 c++ 初学者来说已经足够了。如果一个人可以使用 lambda,这是一个很好的解决方案。
【解决方案6】:

如果您不想使用 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。

【讨论】:

  • @bitmask 当然如果你有 lambdas 你有std::iota,不是吗?
  • @bitmask 但这需要 C++11 :)
  • 非常不直观和不精简的是itend 是在for 循环之外定义的。这有什么原因吗?
  • @ChristianRau:这是一种避免重复调用std::vector&lt;T&gt;::end() 而不必多次键入迭代器类型名称std::vector&lt;int&gt;::const_iterator 的方法,只是一种习惯。在我的真实代码中我也是这样做的,这通常没关系,因为函数太短以至于itend 的范围非常小。现在,您可能会争辩说调用end() 非常便宜,我应该只输入for ( std::vector&lt;int&gt;::const_iterator it = ivec.begin(); it != ivec.end(); ++it ) 但那行要长得多。 :-)
  • @ChristianRau:说实话:在实践中,我使用我正在编辑的文件中的代码使用的任何样式,即我认为一致性高于我们目前提到的优点或缺点。
【解决方案7】:

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

但我不会推荐它。 :)

【讨论】:

  • OP 不能使用 C++11(没有lambas)
【解决方案8】:

还有另一种选择 - 不使用 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. 作为参考传递的项目,因此,它在向量内是可变的

【讨论】:

    【解决方案9】:

    说到提升:

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

    【讨论】:

      【解决方案10】:

      这也有效

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

      【讨论】:

      • 当然,用迭代器手动探索是可行的,但如果 OP 想要这样做,他们就不会询问 stdlib 算法,不是吗?
      【解决方案11】:

      就性能而言,您应该使用reserve() 结合push_back() 函数来初始化向量,如下例所示:

      const int numberOfElements = 10;
      
      std::vector<int> data;
      data.reserve(numberOfElements);
      
      for(int i = 0; i < numberOfElements; i++)
          data.push_back(i);
      

      所有std::fillstd::generate等都在现有向量内容的范围内操作,因此向量必须提前填充一些数据。甚至执行以下操作:std::vector&lt;int&gt; data(10); 创建一个向量,其中所有元素都设置为其默认值(即 int 的情况下为 0)。

      上面的代码避免在用你真正想要的数据填充向量内容之前对其进行初始化。该解决方案的性能在大型数据集上非常明显。

      【讨论】:

        【解决方案12】:

        如果您真的想使用 std::fill 并且仅限于 C++98,则可以使用以下内容,

        #include <algorithm>
        #include <iterator>
        #include <iostream>
        #include <vector>
        
        struct increasing {
            increasing(int start) : x(start) {}
            operator int () const { return x++; }
            mutable int x;
        };
        
        int main(int argc, char* argv[])
        {
            using namespace std;
        
            vector<int> v(10);
            fill(v.begin(), v.end(), increasing(0));
            copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));
            cout << endl;
            return 0;
        }
        

        【讨论】:

          【解决方案13】:

          我知道这是个老问题,但我目前正在使用library 来处理这个问题。它需要 c++14。

          #include "htl.hpp"
          
          htl::Token _;
          
          std::vector<int> vec = _[0, _, 100];
          // or
          for (auto const e: _[0, _, 100]) { ... }
          
          // supports also custom steps
          // _[0, _%3, 100] == 0, 4, 7, 10, ...
          

          【讨论】:

          • 哎呀。这是一些非常难以阅读的代码,而且它在全局范围内也是无效的,因为以_ 开头的标识符被保留给那里的实现。
          【解决方案14】:

          我创建了一个简单的模板函数Sequence(),用于生成数字序列。该功能遵循 R (link) 中的 seq() 函数。这个函数的好处是它可以生成各种数字序列和类型。

          #include <iostream>
          #include <vector>
          
          template <typename T>
          std::vector<T> Sequence(T min, T max, T by) {
            size_t n_elements = ((max - min) / by) + 1;
            std::vector<T> vec(n_elements);
            min -= by;
            for (size_t i = 0; i < vec.size(); ++i) {
              min += by;
              vec[i] = min;
            }
            return vec;
          }
          

          示例用法:

          int main()
          {
              auto vec = Sequence(0., 10., 0.5);
              for(auto &v : vec) {
                  std::cout << v << std::endl;
              }
          }
          

          唯一需要注意的是,所有数字都应该是相同的推断类型。换句话说,对于双精度数或浮点数,所有输入都包含小数,如图所示。

          更新日期:2018 年 6 月 14 日

          【讨论】:

            【解决方案15】:

            brainsandwich 和 underscore_d 给出了非常好的想法。既然填充就是改变内容,那么 STL 算法中最简单的 for_each() 也应该填单:

            std::vector<int> v(10);
            std::for_each(v.begin(), v.end(), [i=0] (int& x) mutable {x = i++;});    
            

            广义捕获[i=o] 赋予 lambda 表达式一个不变量并将其初始化为已知状态(在本例中为 0)。关键字 mutable 允许在每次调用 lambda 时更新此状态。

            只需要稍加修改就可以得到一个正方形序列:

            std::vector<int> v(10);
            std::for_each(v.begin(), v.end(), [i=0] (int& x) mutable {x = i*i; i++;});
            

            生成类似 R 的 seq 并不困难,但请注意,在 R 中,数字模式实际上是双精度数,因此实际上不需要对类型进行参数化。只需使用双精度即可。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-09-22
              • 2016-04-01
              • 2023-03-14
              • 1970-01-01
              • 2012-10-09
              • 2018-10-01
              相关资源
              最近更新 更多