2010-03-31 26 views
464

Supponendo che ho 2 vettori standard:Aggiunta di un vettore per un vettore

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

Diciamo anche dire il entrambi hanno circa 30 elementi.

  • Come si aggiunge il vettore b alla fine del vettore a?

Il modo sporco sarebbe iterando attraverso b e aggiungendo ogni elemento tramite vector<int>::push_back(), anche se non mi piacerebbe farlo!

+29

immagino tutti potranno inviare le risposte utilizzando iteratori. Non ho mai capito perché il vettore non ha op + =() o una funzione append(). –

+22

@Neil Perché 'insert' è sufficiente? –

+17

@Andreas Bene, non si potrebbe dire lo stesso per std :: string? Naturalmente inserire() è sufficiente, ma la sua tutt'altro che scontata nella sua risposta che ciò che sta realmente accadendo è un vettore di essere allegato ad un altro. a + = b lo rende trasparente. –

risposta

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

o

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

La seconda variante è una soluzione più genericamente applicabili, come b potrebbe anche essere un array. Tuttavia, richiede C++ 11

+9

Devo "prenotare" prima di "inserire"? –

+3

@VioletGiraffe reserve non è richiesta ma potrebbe essere consigliabile. È intelligente utilizzare la riserva se si inseriscono ripetutamente in un vettore per il quale si conosce la dimensione finale e tale dimensione è grande. Altrimenti, lascerei che STL sviluppasse il tuo vettore come necessario. – moodboom

+13

Questa soluzione non riesce se si tenta di aggiungere il vettore a se stesso. Genera vettore con dimensioni corrette ma aggiunge elementi vuoti anziché originali. E inizia a funzionare solo se lo anteponi v.reserve (v.size() * 2); (ma può dipendere dall'implementazione STL) – Sergey

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

Questo può essere utilizzato in caso gli elementi nel vettore non hanno un operatore di assegnazione (membro esempio const).

In tutti gli altri casi questa soluzione è ineffiecent rispetto alla soluzione inserto sopra.

+49

Nota che è probabile che sia meno efficiente dell'uso di 'std :: vector <> :: insert()', perché 'std :: copy () 'non può riservare spazio a priori (non ha accesso al vettore stesso, solo a un iteratore che ha), mentre' std :: vector <> :: insert() ', essendo una funzione membro , può. (Ha bisogno di capire che gli iteratori da cui leggere sono iteratori ad accesso casuale per pre-calcolare la lunghezza della sequenza, ma sarebbe un'implementazione debole che non lo farebbe. – sbi

+3

Vero nella pratica, ma in teoria l'implementatore 'std ::' può farlo funzionare. Possono utilizzare internamente estensioni non standard. – MSalters

+7

@MSalter: so che un'implementazione _potrebbe_ fare questo. Questo è il motivo per cui ho scritto che "è probabile che sia meno efficiente". In teoria, un implementatore potrebbe aggiungere un'interfaccia privata a 'std :: back_inserter_iterator >' per consentire un'implementazione di 'std :: copy()' per riconoscerlo e utilizzare questa interfaccia privata per ottenere il possesso del 'std :: vector' e chiama' reserve() 'su di esso. In pratica, tuttavia, è improbabile che un implementatore salti attraverso tutti questi cerchi per ottimizzare un caso d'angolo. – sbi

31

Mentre dire "il compilatore può riservare", perché si basano su di esso? E per quanto riguarda il rilevamento automatico della semantica del movimento? E che dire di tutto quel ripetere il nome del contenitore con lo begin se end s?

non si vuole qualcosa sarebbe, sai, più semplice?

(Scorrere verso il basso per main per la battuta finale)

#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.

Ora con move-data-from-rhs, append-array-to-container, aggiungi forward_list-to-container, sposta-container-da-lhs, grazie alla guida di @DyP.

noti che quanto sopra non viene compilato in CLANG grazie alla tecnica EnableFunctionIf<>.... In clang this workaround funziona.

+0

Penso che sia possibile semplificare questo, ad es. [la parte 'try_reserve'] (http://coliru.stacked-crooked.com/view?id=4ff26889e8134023a0f2d2dd6962b8c7-c59112800dca6ae4674520521ddfe536) – dyp

+0

Dove usi' size_at_least'? (Posso solo vedere la dichiarazione/definizione, ma nessuna chiamata ..) – dyp

+0

Solo aggiunto il ["insert-by-move"] (http://coliru.stacked-crooked.com/view?id=924c7f07cebeea9d727f1df6746d435e-c59112800dca6ae4674520521ddfe536) – dyp

23

Se volete aggiungere vettore a sé entrambe le soluzioni popolari falliranno:

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

A parte l'ultimo tutti i tuoi suggerimenti sono errati come indicato negli altri post ('insert' must non ottenere gli iteratori nel contenitore su cui opera e gli iteratori di 'copia' sono invalidati dall'inserimento tramite' back_inserter'). I due che hai etichettato come "buoni" sembrano funzionare perché non c'è riallocazione (a causa della tua chiamata "reserve"). L'ultima è la strada da percorrere. Un'altra opzione che consentirebbe effettivamente di evitare un secondo contenitore sarebbe quella di utilizzare il ridimensionamento anziché la riserva e quindi copiare la prima metà del vettore sugli elementi appena allocati. – Nobody

Problemi correlati