std :: back_inserter per uno std :: set?


94

Immagino che questa sia una domanda semplice. Devo fare qualcosa del genere:

std::set<int> s1, s2;
s1 = getAnExcitingSet();
std::transform(s1.begin(), s1.end(), std::back_inserter(s2), ExcitingUnaryFunctor());

Certo, std::back_inserternon funziona poiché non c'è push_back. std::inserterserve anche un iteratore? Non l'ho usato std::inserterquindi non sono sicuro di cosa fare.

Qualcuno ha un'idea?


Ovviamente, l'altra mia opzione è usare un vettore per s2, quindi ordinarlo in un secondo momento. Forse è meglio?

Risposte:


139

setnon ha push_backperché la posizione di un elemento è determinata dal comparatore dell'insieme. Usalo std::insertere passalo .begin():

std::set<int> s1, s2;
s1 = getAnExcitingSet();
transform(s1.begin(), s1.end(), 
          std::inserter(s2, s2.begin()), ExcitingUnaryFunctor());

L'iteratore di inserimento chiamerà quindi s2.insert(s2.begin(), x)dove xè il valore passato all'iteratore quando viene scritto su di esso. Il set utilizza l'iteratore come suggerimento su dove inserire. Potresti anche usare s2.end().


2
Dato che inserter(vec, vec.end())funziona anche per i vettori, perché qualcuno usa back_inserter in primo luogo?
NHDaly

7
@NHDaly: perché back_inserter è più veloce
marton78

@ marton78 Ma non dovrebbe essere più veloce solo con un margine molto piccolo, se non del tutto? La chiamata insertinvece che push_backsu un vettore dovrebbe essere più o meno identica (O (1)) quando nessun elemento deve essere spostato.
Felix Dombek

3
@FelixDombek hai ragione, non sarà molto più lento. v.insert(x, v.end())avrà un ramo aggiuntivo all'inizio (a causa dello spostamento di n elementi, ma qui n è zero). Tuttavia, l'uso di inserter1) comunica un intento diverso rispetto all'utilizzo di push_back2) è insolito e fa fermare il lettore e pensa 3) è una pessimazione prematura.
marton78

0

Nel 2016 c'era una proposta per avere un " inserteriteratore a argomento singolo ". https://isocpp.org/files/papers/p0471r0.html . Non sono riuscito a trovare se la proposta fosse avanzata. Penso che abbia senso.

Per ora puoi avere questo comportamento definendo la funzione maker:

template<class Container>
auto sinserter(Container& c){
    using std::end;
    return std::inserter(c, end(c));
}

Usato come:

std::transform(begin(my_vec), end(my_vec), sinserter(my_set), [](auto& e){return e.member;});

È destinato a funzionare su tutti i contenitori standard? Non funziona su std :: forward_list (l'errore del compilatore è "forward_list non ha un membro denominato 'insert'", all'interno dell'istanza di insert_iterator::operator=). Dovrebbe?
Don Hatch

@DonHatch, tutto ciò che ha insert(e end). sembra che forward_listnon abbia insertun'operazione in primo luogo, solo insert_after. E anche se è cambiato non può essere inserito dopo la fine, credo. Non puoi usare un std::listinvece?
alfC

Certo, personalmente non ho bisogno di std :: forward_list. Ma mi interessa un generico "come faccio a copiare un contenitore in un altro?" per tutte le coppie di contenitori per i quali ha senso. Il mio interesse attuale è per esercitare allocatori di container generici come short_alloc di @HowardHinnant.
Don Hatch

@DonHatch, il fatto è che ci sono molte varianti a questa domanda. ("" come faccio a copiare un contenitore in un altro? "). Ad esempio, vuoi preservare i valori originali, vuoi ridurre al minimo le allocazioni, ecc. Per il caso più semplice, la risposta migliore a mio avviso è usare il costruttore il contenitore che richiede due iteratori (la maggior parte dei contenitori può prenderlo) NewContaner new_container(old_other_container.begin(), old_other_container.end()).
alfC

1
Sì, std :: set non è un SequentialContainer, e va bene :-) existing_list = std::list(c.begin(), c.end(), existing_list.get_allocator()) Molto carino, penso che sia la mia risposta. Saluti!
Don Hatch
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.