【问题标题】:Concatenating two std::vectors连接两个 std::vectors
【发布时间】:2010-09-17 03:12:50
【问题描述】:

如何连接两个std::vectors?

【问题讨论】:

  • 给出的答案实际上并没有串联。他们附加了一份副本。可能有一个用途(从效率的角度来看)创建一个 std::vector 连接方法,但是它需要对节点的管理进行一些复杂的共享,这可能是它没有完成的原因。
  • @FauChristian:不,从效率的角度来看可能没有用处。矢量记忆必须是连续的,所以你所建议的是不可能的。如果您想要“对节点管理进行一些复杂的共享”,并且如果您要以这种方式更改向量类,那么您最终会得到一个双端队列。即使那样,以建议的方式重用内存也非常困难,尽管它会开始变得更加可行。我认为目前尚未实施。主要的是,在这样的管理节点(双端队列)共享中,端节点可能部分为空。
  • 只有我一个人想知道为什么在标准库中没有将其实现为a + ba.concat(b)?也许默认实现不是最理想的,但每个数组连接都不需要微优化
  • 多年的演变,所有主流语言中最先进的运算符重载,使语言复杂度翻倍的模板系统,但答案不是 v = v1 + v2;
  • 我的猜测是 STL 不想过度指定语言,以防您希望操作员做一些不同的事情,比如在物理模型中添加力向量。在这种情况下,您可能希望重载 forceVector1 + forceVector2 以清晰、简洁的代码进行逐项添加。

标签: c++ vector stl concatenation stdvector


【解决方案1】:
vector1.insert( vector1.end(), vector2.begin(), vector2.end() );

【讨论】:

  • 我只会添加代码以首先获取每个向量包含的元素数量,并将 vector1 设置为最大的。如果你不这样做,你会做很多不必要的复制。
  • 我有一个问题。如果 vector1 和 vector2 是相同的向量,这会起作用吗?
  • 如果您将多个向量连接到一个,首先在目标向量上调用reserve 是否有帮助?
  • @AlexanderRafferty:仅当vector1.capacity() >= 2 * vector1.size()。这是非典型的,除非你打电话给std::vector::reserve()。否则,向量将重新分配,使作为参数 2 和 3 传递的迭代器无效。
  • 可惜标准库中没有更简洁的表达方式。 .concat+= 什么的
【解决方案2】:

如果您使用的是 C++11,并且希望移动元素而不仅仅是复制它们,您可以使用 std::move_iterator 以及插入(或复制):

#include <vector>
#include <iostream>
#include <iterator>

int main(int argc, char** argv) {
  std::vector<int> dest{1,2,3,4,5};
  std::vector<int> src{6,7,8,9,10};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  // Print out concatenated vector.
  std::copy(
      dest.begin(),
      dest.end(),
      std::ostream_iterator<int>(std::cout, "\n")
    );

  return 0;
}

这对于带有整数的示例不会更有效,因为移动它们并不比复制它们更有效,但是对于具有优化移动的数据结构,它可以避免复制不必要的状态:

#include <vector>
#include <iostream>
#include <iterator>

int main(int argc, char** argv) {
  std::vector<std::vector<int>> dest{{1,2,3,4,5}, {3,4}};
  std::vector<std::vector<int>> src{{6,7,8,9,10}};

  // Move elements from src to dest.
  // src is left in undefined but safe-to-destruct state.
  dest.insert(
      dest.end(),
      std::make_move_iterator(src.begin()),
      std::make_move_iterator(src.end())
    );

  return 0;
}

移动后,src 的元素处于未定义但可安全销毁的状态,其之前的元素在最后直接转移到 dest 的新元素。

【讨论】:

  • 在尝试连接 std::unique_ptr 的 std::vectors 时,std::make_move_iterator() 方法帮助了我。
  • 这和std::move(src.begin(), src.end(), back_inserter(dest))有什么区别?
  • @kshenoy, insert 可能会一次性分配必要的内存量。当back_inserter 可能导致多次重新分配时
【解决方案3】:

我会使用insert function,类似于:

vector<int> a, b;
//fill with data
b.insert(b.end(), a.begin(), a.end());

【讨论】:

    【解决方案4】:

    或者你可以使用:

    std::copy(source.begin(), source.end(), std::back_inserter(destination));
    

    如果两个向量不包含完全相同类型的事物,则此模式很有用,因为您可以使用某些东西而不是 std::back_inserter 来从一种类型转换为另一种类型。

    【讨论】:

    • 复制方法不是一个好方法。它将多次调用 push_back,这意味着如果必须插入大量元素,这可能意味着多次重新分配。最好使用插入,因为向量实现可以进行一些优化以避免重新分配。它可以在开始复制之前保留内存
    • @Yogesh:同意,但没有什么能阻止你先打电话给reservestd::copy 有时有用的原因是如果你想使用除 back_inserter 以外的东西。
    • 当你说“多次分配”时,这是真的——但分配的数量是最坏的日志(添加的条目数)——这意味着添加条目的成本在数量上是恒定的添加的条目。 (基本上,除非分析表明您需要预留空间,否则不必担心)。
    • 复制很糟糕,即使有保留。 vector::insert 将避免所有检查:quick-bench.com/bLJO4OfkAzMcWia7Pa80ynwmAIA
    • @SamuelLi - 如果有问题,主要是 push_back 中的 if &gt; capacity_resize 中的 memset 无关紧要,这已经够麻烦了。
    【解决方案5】:

    对于 C++11,我更喜欢将向量 b 附加到 a:

    std::move(b.begin(), b.end(), std::back_inserter(a));
    

    ab 不重叠时,b 将不再使用。


    这是来自&lt;algorithm&gt;std::move,而不是来自&lt;utility&gt;通常 std::move

    【讨论】:

    • 如果 a 实际上是 b,则行为未定义(如果您知道这永远不会发生,这没关系 - 但在通用代码中值得注意)。
    • @MartinBonner 感谢您提及这一点。可能我应该回到旧的insert 方式更安全。
    • 啊,另一个 std::move。第一次看到就很迷茫。
    • 这与insert()move_iterators 有什么不同吗?如果有,怎么做?
    • 我已经添加了一条关于我们在这里谈论的std::move 的注释,因为大多数人不知道这种过载。希望这是一个改进。
    【解决方案6】:
    std::vector<int> first;
    std::vector<int> second;
    
    first.insert(first.end(), second.begin(), second.end());
    

    【讨论】:

      【解决方案7】:

      我更喜欢已经提到的一个:

      a.insert(a.end(), b.begin(), b.end());
      

      但是如果你使用 C++11,还有一种更通用的方式:

      a.insert(std::end(a), std::begin(b), std::end(b));
      

      另外,这不是问题的一部分,但建议在附加之前使用reserve 以获得更好的性能。如果您将向量与其自身连接,而不保留它会失败,因此您始终应该reserve


      所以基本上你需要什么:

      template <typename T>
      void Append(std::vector<T>& a, const std::vector<T>& b)
      {
          a.reserve(a.size() + b.size());
          a.insert(a.end(), b.begin(), b.end());
      }
      

      【讨论】:

      • std:: 是通过argument-dependent lookup 推导出来的。 end(a) 就足够了。
      • @Asu ADL 只会在 a 的类型来自 std 时添加 std::,这会破坏通用方面。
      • 好点。在这种情况下,它是一个向量,所以无论如何它都可以工作,但是是的,这是一个更好的解决方案。
      • std::begin()/end() 是为没有将它们作为成员函数的集合(如数组)添加的。但是数组也没有 insert() 成员函数,并调用问题“是否有一个带有 insert() 但没有 begin() 的集合(与 std::begin() 一起使用)?”
      • 您不应该使用储备,因为它可能会带来巨大的开销。看这里:stackoverflow.com/a/64102335/7110367
      【解决方案8】:

      使用range v3,您可能会有一个惰性连接:

      ranges::view::concat(v1, v2)
      

      Demo.

      【讨论】:

      • 我预测这将是 2023 年左右的合适答案。
      【解决方案9】:

      连接的一般性能提升是检查向量的大小。并将较小的与较大的合并/插入。

      //vector<int> v1,v2;
      if(v1.size()>v2.size()) {
          v1.insert(v1.end(),v2.begin(),v2.end());
      } else {
          v2.insert(v2.end(),v1.begin(),v1.end());
      }
      

      【讨论】:

      • 这么简单,但我从来没有这么想过!
      • 示例代码不正确。 v1.insert(v2.end()... 使用迭代器进入 v2 以指定 v1 中的位置。
      • 您也可以使用快速交换。 @DavidStone 我对其进行了编辑,以便可以更改连接顺序。是否可以添加到向量的开头?
      • 您可以插入到开头,但这会比较慢。然而,要真正“连接”,顺序通常很重要,所以这就是您需要做的。
      • 我不喜欢这个答案,因为您不会在所有情况下都在 v1 之后插入 v2(没有用注释指定它)。否则,如果您添加一个将串联保存在另一个向量中而不是修改其中一个向量的解决方案,您的答案可能会更完整。
      【解决方案10】:

      如果您希望能够简洁地连接向量,您可以重载 += 运算符。

      template <typename T>
      std::vector<T>& operator +=(std::vector<T>& vector1, const std::vector<T>& vector2) {
          vector1.insert(vector1.end(), vector2.begin(), vector2.end());
          return vector1;
      }
      

      那么你可以这样称呼它:

      vector1 += vector2;
      

      【讨论】:

        【解决方案11】:

        你应该使用vector::insert

        v1.insert(v1.end(), v2.begin(), v2.end());
        

        【讨论】:

        • 这和2008年Tom Ritter和Robert Gamble给出的答案不一样吗?
        【解决方案12】:

        如果你对强异常保证感兴趣(当复制构造函数可以抛出异常时):

        template<typename T>
        inline void append_copy(std::vector<T>& v1, const std::vector<T>& v2)
        {
            const auto orig_v1_size = v1.size();
            v1.reserve(orig_v1_size + v2.size());
            try
            {
                v1.insert(v1.end(), v2.begin(), v2.end());
            }
            catch(...)
            {
                v1.erase(v1.begin() + orig_v1_size, v1.end());
                throw;
            }
        }
        

        如果向量元素的移动构造函数可以抛出(不太可能但仍然存在),则一般无法实现具有强保证的类似append_move

        【讨论】:

        • 难道v1.erase(...也不能扔吗?
        • insert 已经处理了这个问题。此外,对erase 的调用等效于resize
        【解决方案13】:

        C++17中有一个算法std::merge,在对输入向量进行排序时非常好用,

        下面是例子:

        #include <iostream>
        #include <vector>
        #include <algorithm>
        
        int main()
        {
            //DATA
            std::vector<int> v1{2,4,6,8};
            std::vector<int> v2{12,14,16,18};
        
            //MERGE
            std::vector<int> dst;
            std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), std::back_inserter(dst));
        
            //PRINT
            for(auto item:dst)
                std::cout<<item<<" ";
        
            return 0;
        }
        

        【讨论】:

        • 我不认为它比std::vector::insert 更容易使用,但它有不同的用途:将两个范围合并到一个新范围中,而不是在另一个范围的末尾插入一个向量。值得在答案中提及?
        • 好的。我理解答案中的预期。我会补充的。
        【解决方案14】:

        将此添加到您的头文件中:

        template <typename T> vector<T> concat(vector<T> &a, vector<T> &b) {
            vector<T> ret = vector<T>();
            copy(a.begin(), a.end(), back_inserter(ret));
            copy(b.begin(), b.end(), back_inserter(ret));
            return ret;
        }
        

        并以这种方式使用它:

        vector<int> a = vector<int>();
        vector<int> b = vector<int>();
        
        a.push_back(1);
        a.push_back(2);
        b.push_back(62);
        
        vector<int> r = concat(a, b);
        

        r 将包含 [1,2,62]

        【讨论】:

        • 不知道为什么这被否决了。这可能不是最有效的方法,但它没有错,而且很有效。
        • 如果您将相同的向量作为两个参数传入以将向量与自身连接起来,它就会起作用。
        • @leeo non const ref args for one
        【解决方案15】:

        如果您的目标只是为了只读目的而迭代值的范围,另一种方法是将两个向量包装在代理周围 (O(1)) 而不是复制它们 (O(n)),因此它们立即被视为一个单一的,连续的。

        std::vector<int> A{ 1, 2, 3, 4, 5};
        std::vector<int> B{ 10, 20, 30 };
        
        VecProxy<int> AB(A, B);  // ----> O(1)!
        
        for (size_t i = 0; i < AB.size(); i++)
            std::cout << AB[i] << " ";  // ----> 1 2 3 4 5 10 20 30
        

        请参阅https://stackoverflow.com/a/55838758/2379625 了解更多详细信息,包括“VecProxy”实现以及优缺点。

        【讨论】:

          【解决方案16】:

          这是一个使用 C++11 移动语义的通用解决方案:

          template <typename T>
          std::vector<T> concat(const std::vector<T>& lhs, const std::vector<T>& rhs)
          {
              if (lhs.empty()) return rhs;
              if (rhs.empty()) return lhs;
              std::vector<T> result {};
              result.reserve(lhs.size() + rhs.size());
              result.insert(result.cend(), lhs.cbegin(), lhs.cend());
              result.insert(result.cend(), rhs.cbegin(), rhs.cend());
              return result;
          }
          
          template <typename T>
          std::vector<T> concat(std::vector<T>&& lhs, const std::vector<T>& rhs)
          {
              lhs.insert(lhs.cend(), rhs.cbegin(), rhs.cend());
              return std::move(lhs);
          }
          
          template <typename T>
          std::vector<T> concat(const std::vector<T>& lhs, std::vector<T>&& rhs)
          {
              rhs.insert(rhs.cbegin(), lhs.cbegin(), lhs.cend());
              return std::move(rhs);
          }
          
          template <typename T>
          std::vector<T> concat(std::vector<T>&& lhs, std::vector<T>&& rhs)
          {
              if (lhs.empty()) return std::move(rhs);
              lhs.insert(lhs.cend(), std::make_move_iterator(rhs.begin()), std::make_move_iterator(rhs.end()));
              return std::move(lhs);
          }
          

          请注意这与 appending 和 vector 有何不同。

          【讨论】:

          • 第一次调用第二次重载
          【解决方案17】:

          您可以为 + 运算符准备自己的模板:

          template <typename T> 
          inline T operator+(const T & a, const T & b)
          {
              T res = a;
              res.insert(res.end(), b.begin(), b.end());
              return res;
          }
          

          接下来 - 只需使用 +:

          vector<int> a{1, 2, 3, 4};
          vector<int> b{5, 6, 7, 8};
          for (auto x: a + b)
              cout << x << " ";
          cout << endl;
          

          这个例子给出了输出:

          1 2 3 4 5 6 7 8

          【讨论】:

          • 使用T operator+(const T &amp; a, const T &amp; b)很危险,最好使用vector&lt;T&gt; operator+(const vector&lt;T&gt; &amp; a, const vector&lt;T&gt; &amp; b)
          【解决方案18】:
          vector<int> v1 = {1, 2, 3, 4, 5};
          vector<int> v2 = {11, 12, 13, 14, 15};
          copy(v2.begin(), v2.end(), back_inserter(v1));
          

          【讨论】:

          【解决方案19】:

          我已经实现了这个函数,它可以连接任意数量的容器,从右值引用移动并以其他方式复制

          namespace internal {
          
          // Implementation detail of Concatenate, appends to a pre-reserved vector, copying or moving if
          // appropriate
          template<typename Target, typename Head, typename... Tail>
          void AppendNoReserve(Target* target, Head&& head, Tail&&... tail) {
              // Currently, require each homogenous inputs. If there is demand, we could probably implement a
              // version that outputs a vector whose value_type is the common_type of all the containers
              // passed to it, and call it ConvertingConcatenate.
              static_assert(
                      std::is_same_v<
                              typename std::decay_t<Target>::value_type,
                              typename std::decay_t<Head>::value_type>,
                      "Concatenate requires each container passed to it to have the same value_type");
              if constexpr (std::is_lvalue_reference_v<Head>) {
                  std::copy(head.begin(), head.end(), std::back_inserter(*target));
              } else {
                  std::move(head.begin(), head.end(), std::back_inserter(*target));
              }
              if constexpr (sizeof...(Tail) > 0) {
                  AppendNoReserve(target, std::forward<Tail>(tail)...);
              }
          }
          
          template<typename Head, typename... Tail>
          size_t TotalSize(const Head& head, const Tail&... tail) {
              if constexpr (sizeof...(Tail) > 0) {
                  return head.size() + TotalSize(tail...);
              } else {
                  return head.size();
              }
          }
          
          }  // namespace internal
          
          /// Concatenate the provided containers into a single vector. Moves from rvalue references, copies
          /// otherwise.
          template<typename Head, typename... Tail>
          auto Concatenate(Head&& head, Tail&&... tail) {
              size_t totalSize = internal::TotalSize(head, tail...);
              std::vector<typename std::decay_t<Head>::value_type> result;
              result.reserve(totalSize);
              internal::AppendNoReserve(&result, std::forward<Head>(head), std::forward<Tail>(tail)...);
              return result;
          }
          

          【讨论】:

            【解决方案20】:

            这个解决方案可能有点复杂,但boost-range 还提供了一些其他的好东西。

            #include <iostream>
            #include <vector>
            #include <boost/range/algorithm/copy.hpp>
            
            int main(int, char**) {
                std::vector<int> a = { 1,2,3 };
                std::vector<int> b = { 4,5,6 };
                boost::copy(b, std::back_inserter(a));
                for (auto& iter : a) {
                    std::cout << iter << " ";
                }
                return EXIT_SUCCESS;
            }
            

            通常的意图是将向量ab 组合起来,只是对其进行一些操作进行迭代。在这种情况下,有一个可笑的简单join 函数。

            #include <iostream>
            #include <vector>
            #include <boost/range/join.hpp>
            #include <boost/range/algorithm/copy.hpp>
            
            int main(int, char**) {
                std::vector<int> a = { 1,2,3 };
                std::vector<int> b = { 4,5,6 };
                std::vector<int> c = { 7,8,9 };
                // Just creates an iterator
                for (auto& iter : boost::join(a, boost::join(b, c))) {
                    std::cout << iter << " ";
                }
                std::cout << "\n";
                // Can also be used to create a copy
                std::vector<int> d;
                boost::copy(boost::join(a, boost::join(b, c)), std::back_inserter(d));
                for (auto& iter : d) {
                    std::cout << iter << " ";
                }
                return EXIT_SUCCESS;
            }
            

            对于大型向量,这可能是一个优势,因为没有复制。它还可以用于轻松地将泛化复制到多个容器。

            由于某种原因,没有像 boost::join(a,b,c) 这样的东西,这可能是合理的。

            【讨论】:

              【解决方案21】:

              对于提供push_back(字符串、向量、双端队列...)的容器:

              std::copy(std::begin(input), std::end(input), std::back_inserter(output))

              对于提供insert(地图、集)的容器:

              std::copy(std::begin(input), std::end(input), std::inserter(output, output.end()))

              【讨论】:

                【解决方案22】:

                如果您正在寻找一种在创建后将向量附加到另一个向量的方法,vector::insert 是您的最佳选择,正如已多次回答的那样,例如:

                vector<int> first = {13};
                const vector<int> second = {42};
                
                first.insert(first.end(), second.cbegin(), second.cend());
                

                遗憾的是没有办法构造const vector&lt;int&gt;,如上你必须构造然后insert


                如果您真正需要的是一个容器来保存这两个vector&lt;int&gt;s 的串联,那么您可能会得到更好的东西,如果:

                1. 您的vector 包含原语
                2. 您包含的基元大小为 32 位或更小
                3. 你想要一个const 容器

                如果以上都是真的,我建议使用basic_string,他的char_type 与您的vector 中包含的基元的大小相匹配。您应该在代码中包含 static_assert 以验证这些大小是否保持一致:

                static_assert(sizeof(char32_t) == sizeof(int));
                

                有了这个,你可以这样做:

                const u32string concatenation = u32string(first.cbegin(), first.cend()) + u32string(second.cbegin(), second.cend());
                

                有关stringvector 之间差异的更多信息,您可以查看此处:https://stackoverflow.com/a/35558008/2642059

                有关此代码的实时示例,您可以查看此处:http://ideone.com/7Iww3I

                【讨论】:

                  【解决方案23】:

                  您可以使用预实现的 STL 算法,使用模板来实现多态类型的使用。

                  #include <iostream>
                  #include <vector>
                  #include <algorithm>
                  
                  template<typename T>
                  
                  void concat(std::vector<T>& valuesa, std::vector<T>& valuesb){
                  
                       for_each(valuesb.begin(), valuesb.end(), [&](int value){ valuesa.push_back(value);});
                  }
                  
                  int main()
                  {
                      std::vector<int> values_p={1,2,3,4,5};
                      std::vector<int> values_s={6,7};
                  
                     concat(values_p, values_s);
                  
                      for(auto& it : values_p){
                  
                          std::cout<<it<<std::endl;
                      }
                  
                      return 0;
                  }
                  

                  如果您不想进一步使用第二个向量,可以清除它(clear() 方法)。

                  【讨论】:

                    【解决方案24】:

                    将两个std::vector-sfor 循环连接成一个std::vector

                        std::vector <int> v1 {1, 2, 3}; //declare vector1
                        std::vector <int> v2 {4, 5}; //declare vector2
                        std::vector <int> suma; //declare vector suma
                    
                        for(int i = 0; i < v1.size(); i++) //for loop 1
                        {
                             suma.push_back(v1[i]);
                        }
                    
                        for(int i = 0; i< v2.size(); i++) //for loop 2
                        {
                             suma.push_back(v2[i]);
                        }
                    
                        for(int i = 0; i < suma.size(); i++) //for loop 3-output
                        {
                             std::cout << suma[i];
                        }
                    

                    【讨论】:

                    • 除了不工作之外,这段代码非常不习惯。您至少应该使用 auto 迭代器而不是手动索引。你不关心你连接的索引是什么,只是它是按顺序完成的。
                    • @TarickWelling 我不明白你为什么说这段代码不起作用,你能更具体一点吗?
                    • 你查看我评论的日期了吗?您修复了代码中的错误,现在它不是惯用的。
                    【解决方案25】:

                    尝试,创建两个向量并将第二个向量添加到第一个向量, 代码:

                    std::vector<int> v1{1,2,3};
                    std::vector<int> v2{4,5};
                    
                    for(int i = 0; i<v2.size();i++)
                    {
                         v1.push_back(v2[i]);
                    }
                    

                    v1:1,2,3.

                    说明:

                    当 i int 不是 v2 大小时,推回元素,在 v1 向量中索引 i。

                    【讨论】:

                    • 您的描述不清楚(而且没用,抱歉)。否则,如果您添加第二个解决方案,将串联保存在另一个向量中而不是修改其中一个,您的答案可能会更完整。
                    【解决方案26】:

                    说实话,您可以通过将两个向量中的元素复制到另一个向量中来快速连接两个向量,或者只附加两个向量中的一个!这取决于你的目标。

                    方法一:分配新向量,其大小为两个原始向量的大小之和。

                    vector<int> concat_vector = vector<int>();
                    concat_vector.setcapacity(vector_A.size() + vector_B.size());
                    // Loop for copy elements in two vectors into concat_vector
                    

                    方法2:通过添加/插入向量B的元素来追加向量A。

                    // Loop for insert elements of vector_B into vector_A with insert() 
                    function: vector_A.insert(vector_A .end(), vector_B.cbegin(), vector_B.cend());
                    

                    【讨论】:

                    • 您的答案添加了什么其他答案中尚未提供的内容?
                    • @Mat:粗体字。
                    • 如果之后不再需要原始向量,最好使用std::move_iterator 以便移动元素而不是复制元素。 (见en.cppreference.com/w/cpp/iterator/move_iterator)。
                    • 什么是setcapacityfunction: 是什么?
                    • @L.F.我认为他在谈论resize 方法。
                    猜你喜欢
                    • 1970-01-01
                    • 2015-04-17
                    • 1970-01-01
                    • 2021-02-02
                    • 2020-02-20
                    • 2017-10-15
                    • 1970-01-01
                    相关资源
                    最近更新 更多