2010-03-31 44 views
464

Giả sử tôi có 2 vectơ tiêu chuẩn:Phụ thêm một vector để một vector

vector<int> a; 
vector<int> b; 

Cũng giả sử cả hai có khoảng 30 nguyên tố.

  • Làm cách nào để thêm vectơ b vào cuối vectơ a?

Cách bẩn sẽ lặp qua b và thêm từng phần tử qua vector<int>::push_back(), mặc dù tôi không muốn làm điều đó!

+29

Tôi đoán mọi người sẽ gửi câu trả lời sử dụng vòng lặp. Tôi chưa bao giờ làm việc tại sao vector không có hàm op + =() hoặc append(). –

+22

@Neil Vì 'chèn' là đủ? –

+17

@Andreas Vâng, không thể nói như vậy cho std :: string? Tất nhiên chèn() là đủ, nhưng nó xa rõ ràng trong câu trả lời của bạn rằng những gì đang thực sự xảy ra là một vector được nối vào nhau. a + = b làm cho điều này minh bạch. –

Trả lời

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

hoặc

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

Các biến thể thứ hai là một giải pháp tổng quát áp dụng hơn, như b cũng có thể là một mảng. Tuy nhiên, yêu cầu C++ 11

+9

Tôi có cần 'đặt trước' trước khi chèn'? –

+3

@VioletGiraffe dự trữ là không cần thiết nhưng có thể được khuyến khích. Đó là thông minh để sử dụng dự trữ nếu bạn liên tục chèn vào một vector mà bạn biết kích thước cuối cùng, và kích thước đó là lớn. Nếu không, tôi sẽ để STL phát triển vectơ của bạn khi cần thiết. – moodboom

+13

Giải pháp này không thành công nếu bạn cố gắng gắn thêm vectơ vào chính nó. Nó tạo ra vectơ với kích thước chính xác nhưng thêm các phần tử rỗng thay vì nguyên bản. Và nó bắt đầu chỉ hoạt động nếu bạn thêm vào nó bằng v.reserve (v.size() * 2); (nhưng nó có thể phụ thuộc vào việc thực hiện STL) – Sergey

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

Điều này có thể được sử dụng trong trường hợp các mục trong vector không có toán tử gán (ví dụ: thành viên const).

Trong tất cả các trường hợp khác, giải pháp này không hiệu quả so với giải pháp chèn ở trên.

+49

Lưu ý rằng điều này có thể kém hiệu quả hơn khi sử dụng 'std :: vector <> :: insert()', vì 'std :: copy () 'không thể dự trữ đủ không gian trước đó (nó không có quyền truy cập vào chính vectơ, chỉ với một trình lặp có), trong khi' std :: vector <> :: insert() ', là một hàm thành viên , có thể. (Nó cần phải tìm ra rằng các trình vòng lặp để đọc từ là các trình vòng lặp truy cập ngẫu nhiên để tính toán trước độ dài của chuỗi, nhưng nó sẽ là một sự triển khai yếu kém mà sẽ không làm điều này.) – sbi

+3

Đúng trong thực tế, nhưng về mặt lý thuyết, người triển khai 'std ::' có thể làm cho nó hoạt động. Họ có thể sử dụng tiện ích mở rộng không chuẩn trong nội bộ. – MSalters

+7

@MSalter: Tôi biết rằng việc triển khai _could_ thực hiện việc này. Đây là lý do tại sao tôi viết nó "có khả năng kém hiệu quả hơn". Về lý thuyết, một người triển khai có thể thêm một giao diện riêng vào 'std :: back_inserter_iterator >' để cho phép thực hiện 'std :: copy()' để nhận ra nó và sử dụng giao diện riêng này để nắm giữ 'std :: vector' và gọi' reserve() 'trên nó. Tuy nhiên, trong thực tế, không có người triển khai nào nhảy qua tất cả các vòng này để tối ưu hóa trường hợp góc như vậy. – sbi

31

Trong khi nói "trình biên dịch có thể đặt trước", tại sao lại dựa vào nó? Và những gì về tự động phát hiện các ngữ nghĩa di chuyển? Và điều gì về việc lặp lại tên vùng chứa với begin s và end?

Bạn sẽ không muốn điều gì đó đơn giản hơn?

(Cuộn xuống main cho rằng phần cuối)

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

template<typename C,typename=void> struct can_reserve: std::false_type {}; 

template<typename T, typename A> 
struct can_reserve<std::vector<T,A>,void>: 
    std::true_type 
{}; 

template<int n> struct secret_enum { enum class type {}; }; 
template<int n> 
using SecretEnum = typename secret_enum<n>::type; 

template<bool b, int override_num=1> 
using EnableFuncIf = typename std::enable_if< b, SecretEnum<override_num> >::type; 
template<bool b, int override_num=1> 
using DisableFuncIf = EnableFuncIf< !b, -override_num >; 

template<typename C, EnableFuncIf< can_reserve<C>::value >... > 
void try_reserve(C& c, std::size_t n) { 
    c.reserve(n); 
} 
template<typename C, DisableFuncIf< can_reserve<C>::value >... > 
void try_reserve(C& c, std::size_t) { } // do nothing 

template<typename C,typename=void> 
struct has_size_method:std::false_type {}; 
template<typename C> 
struct has_size_method<C, typename std::enable_if<std::is_same< 
    decltype(std::declval<C>().size()), 
    decltype(std::declval<C>().size()) 
>::value>::type>:std::true_type {}; 

namespace adl_aux { 
    using std::begin; using std::end; 
    template<typename C> 
    auto adl_begin(C&&c)->decltype(begin(std::forward<C>(c))); 
    template<typename C> 
    auto adl_end(C&&c)->decltype(end(std::forward<C>(c))); 
} 
template<typename C> 
struct iterable_traits { 
    typedef decltype(adl_aux::adl_begin(std::declval<C&>())) iterator; 
    typedef decltype(adl_aux::adl_begin(std::declval<C const&>())) const_iterator; 
}; 
template<typename C> using Iterator = typename iterable_traits<C>::iterator; 
template<typename C> using ConstIterator = typename iterable_traits<C>::const_iterator; 
template<typename I> using IteratorCategory = typename std::iterator_traits<I>::iterator_category; 

template<typename C, EnableFuncIf< has_size_method<C>::value, 1>... > 
std::size_t size_at_least(C&& c) { 
    return c.size(); 
} 

template<typename C, EnableFuncIf< !has_size_method<C>::value && 
    std::is_base_of< std::random_access_iterator_tag, IteratorCategory<Iterator<C>> >::value, 2>... > 
std::size_t size_at_least(C&& c) { 
    using std::begin; using std::end; 
    return end(c)-begin(c); 
}; 
template<typename C, EnableFuncIf< !has_size_method<C>::value && 
    !std::is_base_of< std::random_access_iterator_tag, IteratorCategory<Iterator<C>> >::value, 3>... > 
std::size_t size_at_least(C&& c) { 
    return 0; 
}; 

template < typename It > 
auto try_make_move_iterator(It i, std::true_type) 
-> decltype(make_move_iterator(i)) 
{ 
    return make_move_iterator(i); 
} 
template < typename It > 
It try_make_move_iterator(It i, ...) 
{ 
    return i; 
} 


#include <iostream> 
template<typename C1, typename C2> 
C1&& append_containers(C1&& c1, C2&& c2) 
{ 
    using std::begin; using std::end; 
    try_reserve(c1, size_at_least(c1) + size_at_least(c2)); 

    using is_rvref = std::is_rvalue_reference<C2&&>; 
    c1.insert(end(c1), 
      try_make_move_iterator(begin(c2), is_rvref{}), 
      try_make_move_iterator(end(c2), is_rvref{})); 

    return std::forward<C1>(c1); 
} 

struct append_infix_op {} append; 
template<typename LHS> 
struct append_on_right_op { 
    LHS lhs; 
    template<typename RHS> 
    LHS&& operator=(RHS&& rhs) { 
    return append_containers(std::forward<LHS>(lhs), std::forward<RHS>(rhs)); 
    } 
}; 

template<typename LHS> 
append_on_right_op<LHS> operator+(LHS&& lhs, append_infix_op) { 
    return { std::forward<LHS>(lhs) }; 
} 
template<typename LHS,typename RHS> 
typename std::remove_reference<LHS>::type operator+(append_on_right_op<LHS>&& lhs, RHS&& rhs) { 
    typename std::decay<LHS>::type retval = std::forward<LHS>(lhs.lhs); 
    return append_containers(std::move(retval), std::forward<RHS>(rhs)); 
} 

template<typename C> 
void print_container(C&& c) { 
    for(auto&& x:c) 
    std::cout << x << ","; 
    std::cout << "\n"; 
}; 

int main() { 
    std::vector<int> a = {0,1,2}; 
    std::vector<int> b = {3,4,5}; 
    print_container(a); 
    print_container(b); 
    a +append= b; 
    const int arr[] = {6,7,8}; 
    a +append= arr; 
    print_container(a); 
    print_container(b); 
    std::vector<double> d = (std::vector<double>{-3.14, -2, -1} +append= a); 
    print_container(d); 
    std::vector<double> c = std::move(d) +append+ a; 
    print_container(c); 
    print_container(d); 
    std::vector<double> e = c +append+ std::move(a); 
    print_container(e); 
    print_container(a); 
} 

hehe.

Bây giờ với di chuyển-dữ liệu-từ-rh, nối thêm mảng-to-container, nối thêm forward_list-to-container, di chuyển-container-từ-lhs, nhờ trợ giúp của @ DyP.

Lưu ý rằng ở trên không biên dịch trong tiếng kêu nhờ kỹ thuật EnableFunctionIf<>.... Trong clang this workaround hoạt động.

+0

Tôi nghĩ bạn có thể đơn giản hóa điều này, ví dụ: [phần 'try_reserve'] (http://coliru.stacked-crooked.com/view?id=4ff26889e8134023a0f2d2dd6962b8c7-c59112800dca6ae4674520521ddfe536) – dyp

+0

Bạn sử dụng' size_at_least' ở đâu? (Tôi chỉ có thể xem tuyên bố/định nghĩa, nhưng không gọi ..) – dyp

+0

Chỉ thêm ["chèn-by-move"] (http://coliru.stacked-crooked.com/view?id=924c7f07cebeea9d727f1df6746d435e-c59112800dca6ae4674520521ddfe536) – dyp

23

Nếu bạn muốn thêm vector để bản thân cả hai giải pháp phổ biến sẽ thất bại:

std::vector<std::string> v, orig; 

orig.push_back("first"); 
orig.push_back("second"); 

// BAD: 
v = orig; 
v.insert(v.end(), v.begin(), v.end()); 
// Now v contains: { "first", "second", "", "" } 

// BAD: 
v = orig; 
std::copy(v.begin(), v.end(), std::back_inserter(v)); 
// std::bad_alloc exception is generated 

// GOOD, but I can't guarantee it will work with any STL: 
v = orig; 
v.reserve(v.size()*2); 
v.insert(v.end(), v.begin(), v.end()); 
// Now v contains: { "first", "second", "first", "second" } 

// GOOD, but I can't guarantee it will work with any STL: 
v = orig; 
v.reserve(v.size()*2); 
std::copy(v.begin(), v.end(), std::back_inserter(v)); 
// Now v contains: { "first", "second", "first", "second" } 

// GOOD (best): 
v = orig; 
v.insert(v.end(), orig.begin(), orig.end()); // note: we use different vectors here 
// Now v contains: { "first", "second", "first", "second" } 
+2

Ngoài lời cuối cùng, tất cả các đề xuất của bạn đều sai như đã nêu trong các bài viết khác ('chèn' phải không nhận các trình vòng lặp vào vùng chứa nó hoạt động và các trình lặp của 'copy' bị vô hiệu hóa bằng cách chèn thông qua' back_inserter'). Hai bạn dán nhãn là "tốt" dường như làm việc vì không có sự phân bổ lại (vì lệnh gọi 'dự ​​trữ của bạn '). Người cuối cùng là con đường để đi. Một tùy chọn khác thực sự cho phép để tránh một container thứ hai sẽ là sử dụng thay đổi kích thước thay vì dự trữ và sau đó sao chép nửa đầu của vectơ vào các phần tử mới được phân bổ. – Nobody

Các vấn đề liên quan