std :: back_inserter dla std :: set?

98

Myślę, że to proste pytanie. Muszę zrobić coś takiego:

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

Oczywiście std::back_inserternie działa, ponieważ nie ma push_back. std::inserterrównież potrzebuje iteratora? Nie korzystałem, std::inserterwięc nie wiem, co robić.

Czy ktoś ma pomysł?


Oczywiście moją drugą opcją jest użycie wektora dla s2, a następnie posortowanie go później. Może tak jest lepiej?

rlbond
źródło

Odpowiedzi:

143

setnie ma, push_backponieważ pozycja elementu jest określana przez komparator zbioru. Użyj std::inserteri przekaż .begin():

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

Iterator wstawiania wywoła następnie wartość s2.insert(s2.begin(), x)gdzie xjest wartością przekazaną do iteratora podczas zapisu do niego. Zestaw używa iteratora jako wskazówki, gdzie wstawić. Równie dobrze możesz użyć s2.end().

Johannes Schaub - litb
źródło
2
Skoro inserter(vec, vec.end())działa również dla wektorów, dlaczego ktoś w pierwszej kolejności używa back_inserter?
NHDaly
7
@NHDaly: ponieważ back_inserter jest szybszy
marton78
@ marton78 Ale czy nie powinno to być szybsze tylko o bardzo mały margines, jeśli w ogóle? Wywołanie insertzamiast push_backna wektorze powinno być z grubsza identyczne (O (1)), gdy nie trzeba przesuwać żadnych elementów.
Felix Dombek
3
@FelixDombek masz rację, nie będzie dużo wolniej. v.insert(x, v.end())będzie miał dodatkową gałąź na początku (dzięki temu przesuwa n elementów, ale tutaj n wynosi zero). Jednak użycie inserter1) komunikuje inną intencję niż użycie push_back2) jest niezwykłe i sprawia, że ​​czytelnik zatrzymuje się i myśli 3) jest przedwczesną pesymizacją.
marton78,
0

W 2016 r. Pojawiła się propozycja posiadania „ inserteriteratora pojedynczego argumentu ”. https://isocpp.org/files/papers/p0471r0.html . Nie mogłem znaleźć, czy propozycja została wysunięta. Myślę, że to ma sens.

Na razie możesz mieć takie zachowanie definiujące funkcję kreatora:

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

Użyty jako:

std::transform(begin(my_vec), end(my_vec), sinserter(my_set), [](auto& e){return e.member;});
alfC
źródło
Czy to ma działać na wszystkich standardowych kontenerach? Nie działa na std :: forward_list (błąd kompilatora to „forward_list nie ma członka o nazwie„ insert ””, wewnątrz instancji insert_iterator::operator=). Czy powinno?
Don Hatch
@DonHatch, wszystko, co ma insert(i end). wydaje się, że forward_listnie ma insertoperacji w pierwszej kolejności, tylko insert_after. A nawet jeśli to się zmieni, to chyba nie da się wstawić po zakończeniu. Nie możesz użyć std::listzamiast tego?
alfC
Jasne, osobiście nie potrzebuję std :: forward_list. Ale interesuje mnie ogólne „jak skopiować jeden kontener do drugiego?” dla wszystkich par pojemników, dla których ma to sens. Moje obecne zainteresowania dotyczą wykonywania ogólnych alokatorów kontenerów, takich jak short_alloc @HowardHinnant.
Don Hatch
@DonHatch, chodzi o to, że istnieje wiele wariantów tego pytania. („Jak mogę skopiować jeden kontener do innego?”). Na przykład, czy chcesz zachować oryginalne wartości, czy chcesz zminimalizować alokacje itp. W najprostszym przypadku najlepszą odpowiedzią moim zdaniem jest użycie skonstruuj kontener, który przyjmuje dwa iteratory (większość kontenerów może to przyjąć) NewContaner new_container(old_other_container.begin(), old_other_container.end()).
alfC
1
Tak, std :: set nie jest SequentialContainer i to w porządku :-) existing_list = std::list(c.begin(), c.end(), existing_list.get_allocator()) Bardzo fajnie, myślę, że to moja odpowiedź. Twoje zdrowie!
Don Hatch