【问题标题】:Is there a compact equivalent to Python range() in C++/STL在 C++/STL 中是否有与 Python range() 等效的紧凑函数
【发布时间】:2012-10-20 13:48:13
【问题描述】:

如何使用 C++/STL 执行以下等效操作?我想用一系列值 [min, max) 填充std::vector

# Python
>>> x = range(0, 10)
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

我想我可以使用std::generate_n 并提供一个仿函数来生成序列,但我想知道是否有更简洁的方法来使用 STL?

【问题讨论】:

  • 请注意,在 Python2 中,range 返回一个真实的列表。在 Python3 中,range 的工作方式更像 Python2 中的 xrange,并返回一个可迭代的范围对象。 range 还接受第三个参数,即步长或步幅大小。
  • 我想指出这一点:看看你有多大的机会在谷歌搜索中找到它的标题。再加上一个清晰、简洁的问题,包括表现出的努力,我肯定会给我 +1 分。
  • 我知道 range/xrange - 不过还没有开始使用 Python 3。谢谢。

标签: c++ python


【解决方案1】:

在 C++11 中,有 std::iota:

#include <vector>
#include <numeric> //std::iota

std::vector<int> x(10);
std::iota(std::begin(x), std::end(x), 0); //0 is the starting number

【讨论】:

  • iota 代表什么?
  • @FelixDombek,看看this question
  • 不太一样。 iota 填充现有容器,而范围本身是可迭代的,但实际上并不包含值。可以在几十亿个项目上毫无问题地创建 Python 范围,但不要尝试使用 iota。实际上,您必须为此创建一个包含足够元素的容器。
  • @André,我同意,Eric Niebler 在这方面做了一些很棒的工作。他的view::iota (IIRC) 的行为应该与 Python 非常相似,是无限的和懒惰的。
  • std::iota 可以通过包含#include&lt;numeric&gt;来使用
【解决方案2】:

boost::irange

std::vector<int> x;
boost::push_back(x, boost::irange(0, 10));

【讨论】:

  • 啊,Boost 的奇迹 :) 那么这肯定是正确的 C++03 答案。
  • 哇,推一个范围更好。
  • 克里斯,嘿,我正在学习 C++,你为什么这么说?今天不建议使用 Boost 吗?
  • #include &lt;boost/range/algorithm_ext/push_back.hpp&gt; #include &lt;boost/range/irange.hpp&gt;
【解决方案3】:

我最终编写了一些实用程序函数来做到这一点。您可以按如下方式使用它们:

auto x = range(10); // [0, ..., 9]
auto y = range(2, 20); // [2, ..., 19]
auto z = range(10, 2, -2); // [10, 8, 6, 4]

代码:

#include <vector>
#include <stdexcept>

template <typename IntType>
std::vector<IntType> range(IntType start, IntType stop, IntType step)
{
  if (step == IntType(0))
  {
    throw std::invalid_argument("step for range must be non-zero");
  }

  std::vector<IntType> result;
  IntType i = start;
  while ((step > 0) ? (i < stop) : (i > stop))
  {
    result.push_back(i);
    i += step;
  }

  return result;
}

template <typename IntType>
std::vector<IntType> range(IntType start, IntType stop)
{
  return range(start, stop, IntType(1));
}

template <typename IntType>
std::vector<IntType> range(IntType stop)
{
  return range(IntType(0), stop, IntType(1));
}

【讨论】:

  • 加 1,但你也应该让你的 range 函数检查你没有离开 stop ...
  • @Ben:我认为这已经奏效了。 range(5, 10, -1)gives you an empty list.
  • 我用它作为一种简单的方法来获取 foreach 循环的增量变量,然后才意识到这对于这个目的来说非常慢。 boost::irange 更快。
  • 在循环之前调用result.reserve((stop - start)/step)会提高性能吗?
【解决方案4】:

多年来,我一直将这个库用于这个确切的目的:

https://github.com/klmr/cpp11-range

效果很好,并且代理已优化。

for (auto i : range(1, 5))
    cout << i << "\n";

for (auto u : range(0u))
    if (u == 3u) 
        break;
    else         
        cout << u << "\n";

for (auto c : range('a', 'd'))
    cout << c << "\n";

for (auto i : range(100).step(-3))
    if (i < 90) 
        break;
    else        
        cout << i << "\n";

for (auto i : indices({"foo", "bar"}))
    cout << i << '\n';

【讨论】:

    【解决方案5】:

    boost::irange,但不提供浮点,负步,不能直接初始化stl容器。

    my RO library中也有numeric_range

    在 RO 中,初始化一个向量:

    vector<int> V=range(10);
    

    从文档页面剪切-n-粘贴示例(scc - c++ sn-p 评估器):

    // [0,N)  open-ended range. Only range from 1-arg  range() is open-ended.
    scc 'range(5)'
    {0, 1, 2, 3, 4}
    
    // [0,N]  closed range
    scc 'range(1,5)'
    {1, 2, 3, 4, 5}
    
    // floating point 
    scc 'range(1,5,0.5)'
    {1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5}
    
    // negative step
    scc 'range(10,0,-1.5)'
    {10, 8.5, 7, 5.5, 4, 2.5, 1}
    
    // any arithmetic type
    scc "range('a','z')"
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    
    // no need for verbose iota. (vint - vector<int>)
    scc 'vint V = range(5);   V' 
    {0, 1, 2, 3, 4}
    
    // is lazy
    scc 'auto NR = range(1,999999999999999999l);  *find(NR.begin(), NR.end(), 5)'
    5
    
    //  Classic pipe. Alogorithms are from std:: 
    scc 'vint{3,1,2,3} | sort | unique | reverse'
    {3, 2, 1}
    
    //  Assign 42 to 2..5
    scc 'vint V=range(0,9);   range(V/2, V/5) = 42;  V'
    {0, 1, 42, 42, 42, 5, 6, 7, 8, 9}
    
    //  Find (brute force algorithm) maximum of  `cos(x)` in interval: `8 < x < 9`:
    scc 'range(8, 9, 0.01) * cos  || max'
    -0.1455
    
    //  Integrate sin(x) from 0 to pi
    scc 'auto d=0.001;  (range(0,pi,d) * sin || add) * d'
    2
    
    //  Total length of strings in vector of strings
    scc 'vstr V{"aaa", "bb", "cccc"};  V * size ||  add'
    9
    
    //  Assign to c-string, then append `"XYZ"` and then remove `"bc"` substring :
    scc 'char s[99];  range(s) = "abc";  (range(s) << "XYZ") - "bc"'
    aXYZ
    
    
    // Hide phone number:
    scc "str S=\"John Q Public  (650)1234567\";  S|isdigit='X';  S"
    John Q Public  (XXX)XXXXXXX
    

    【讨论】:

      【解决方案6】:

      对于那些不能使用 C++11 或库的人:

      vector<int> x(10,0); // 0 is the starting number, 10 is the range size
      transform(x.begin(),x.end(),++x.begin(),bind2nd(plus<int>(),1)); // 1 is the increment
      

      【讨论】:

        【解决方案7】:

        类似于下面的 range() 函数会有所帮助:

        #include <algorithm>
        #include <iostream>
        #include <numeric>
        #include <vector>
        using namespace std;
        
        // define range function (only once)
        template <typename T>
        vector <T> range(T N1, T N2) {
            vector<T> numbers(N2-N1);
            iota(numbers.begin(), numbers.end(), N1);
            return numbers;
        }
        
        
        vector <int> arr = range(0, 10);
        vector <int> arr2 = range(5, 8);
        
        for (auto n : arr) { cout << n << " "; }    cout << endl;
        // output:    0 1 2 3 4 5 6 7 8 9
        
        for (auto n : arr2) { cout << n << " "; }   cout << endl;
        // output:    5 6 7
        

        【讨论】:

          【解决方案8】:

          我不知道有一种方法可以像在 python 中那样做,但另一种选择显然是 for 循环遍历它:

          for (int i = range1; i < range2; ++i) {
              x.push_back(i);
          }
          

          如果你有 c++11,克里斯的答案会更好

          【讨论】:

          • 远没有iota那么神秘
          【解决方案9】:

          如果不能使用 C++11,可以使用std::partial_sum 生成 1 到 10 的数字。如果需要 0 到 9 的数字,则可以使用 transform 减 1:

          std::vector<int> my_data( 10, 1 );
          std::partial_sum( my_data.begin(), my_data.end(), my_data.begin() );
          std::transform(my_data.begin(), my_data.end(), my_data.begin(), bind2nd(std::minus<int>(), 1));
          

          【讨论】:

          • -1,请正确格式化代码(每行代码前加4个空格,开启等宽字体和语法高亮)
          【解决方案10】:

          前段时间我写了如下_range类,其行为类似于Python range(放到“range.h”中):

          #pragma once
          #include <vector>
          #include <cassert>
          
          template < typename T = size_t >
          class _range 
          {
                  const T kFrom, kEnd, kStep;
          
              public:
          
                  ///////////////////////////////////////////////////////////
                  // Constructor 
                  ///////////////////////////////////////////////////////////
                  //
                  // INPUT:
                  //      from - Starting number of the sequence.
                  //      end - Generate numbers up to, but not including this number.
                  //      step -  Difference between each number in the sequence.     
                  //
                  // REMARKS:
                  //      Parameters must be all positive or all negative
                  //
                  _range( const T from, const T end, const T step = 1 ) 
                      : kFrom( from ), kEnd( end ), kStep( step ) 
                  {
                      assert( kStep != 0 );
                      assert( ( kFrom >= 0 && kEnd > 0 && kStep > 0 ) || ( kFrom < 0 && kEnd < 0 && kStep < 0 ) );
                  }
          
                  // Default from==0, step==1
                  _range( const T end ) 
                      : kFrom( 0 ), kEnd( end ), kStep( 1 ) 
                  {
                      assert( kEnd > 0 );
                  }
          
              public:
          
                  class _range_iter 
                  {
                      T fVal;
                      const T kStep;
                  public:
                      _range_iter( const T v, const T step ) : fVal( v ), kStep( step ) {}
                      operator T  () const            { return fVal; }
                      operator const T & ()           { return fVal; }
                      const T operator * () const     { return fVal; }
                      const _range_iter & operator ++ ()  { fVal += kStep; return * this; }
          
          
                      bool operator == ( const _range_iter & ri ) const
                      {
                          return ! operator != ( ri );
                      }
          
                      bool operator != ( const _range_iter & ri ) const
                      {   
                          // This is a tricky part - when working with iterators
                          // it checks only once for != which must be a hit to stop;
                          // However, this does not work if increasing kStart by N times kSteps skips over kEnd
                          return fVal < 0 ? fVal > ri.fVal : fVal < ri.fVal;  
                      }                                               
                  };                                                  
          
                  const _range_iter begin()   { return _range_iter( kFrom, kStep ); }
                  const _range_iter end()     { return _range_iter( kEnd, kStep ); }
          
              public:
          
                  // Conversion to any vector< T >
                  operator std::vector< T > ( void ) 
                  {
                      std::vector< T > retRange;
                      for( T i = kFrom; i < kEnd; i += kStep )
                          retRange.push_back( i );
                      return retRange;    // use move semantics here
                  }
          };
          
          
          // A helper to use pure range meaning _range< size_t >
          typedef _range<>    range;
          

          一些测试代码如下所示:

          #include "range.h" 
          #include <iterator>
          #include <fstream>
          
          using namespace std;
          
          void RangeTest( void )
          {
              ofstream ostr( "RangeTest.txt" );
              if( ostr.is_open() == false )
                  return;
          
              // 1:
              ostr << "1st test:" << endl;
          
              vector< float > v = _range< float >( 256 );
              copy( v.begin(), v.end(), ostream_iterator< float >( ostr, ", " ) );
          
              // 2:
              ostr << endl << "2nd test:" << endl;
          
              vector< size_t >    v_size_t( range( 0, 100, 13 ) );
              for( auto a : v_size_t )
                  ostr << a << ", ";
          
              // 3:
              ostr << endl << "3rd test:" << endl;
          
              auto vvv = range( 123 );    // 0..122 inclusive, with step 1
              for( auto a : vvv )
                  ostr << a << ", ";
          
              // 4:
              ostr << endl << "4th test:" << endl;
          
              // Can be used in the nested loops as well
              for( auto i : _range< float >( 0, 256, 16.5 ) ) 
              {
                  for( auto j : _range< int >( -2, -16, -3 ) ) 
                  {
                      ostr << j << ", ";
                  }
                  ostr << endl << i << endl;
              }
          
          }
          

          【讨论】:

          • 不错!但我相信你的 operator!= 实现,而不是 fVal &lt; 0 ? fVal &gt; ri.fVal : fVal &lt; ri.fVal; 你想要 kStep &lt; 0 ? fVal &gt; ri.fVal : fVal &lt; ri.fVal; 想想你打电话给 _range( -10, -5, 1 ) 时的例子
          • 嗨!感谢您的评论。但是,为了避免太多的复杂性,这个类不允许像您的示例 _range(-10, -5, 1) 中那样混合符号。这是由 constr 中的断言强制执行的,因此所有 kFrom、kEnd、kStep 都是正数,或者都是负数(没有符号混合)。在运算符 != 中(正如我写的那样,浮点数据有点棘手),我们必须只比较 fVal 和 r.fVal 但要考虑它的符号。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-06
          • 2020-10-10
          • 2012-01-21
          • 1970-01-01
          • 2010-10-30
          相关资源
          最近更新 更多