Apa cara terbaik untuk melakukan iterasi pada dua atau lebih kontainer secara bersamaan

114

C ++ 11 menyediakan banyak cara untuk melakukan iterasi pada container. Sebagai contoh:

Loop berbasis rentang

for(auto c : container) fun(c)

std :: for_each

for_each(container.begin(),container.end(),fun)

Namun apa cara yang disarankan untuk mengulang lebih dari dua (atau lebih) wadah dengan ukuran yang sama untuk mencapai sesuatu seperti:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}
memec
sumber
1
bagaimana dengan transformhadir di #include <algorithm>?
Ankit Acharya
Tentang pengulangan tugas: jika keduanya adalah vektor atau serupa, gunakan containerA = containerB;sebagai pengganti pengulangan.
emlai
Pertanyaan serupa: stackoverflow.com/questions/8511035/…
knedlsepp
1
Kemungkinan duplikat fungsi Sequence-zip untuk c ++ 11?
underscore_d

Jawaban:

53

Agak terlambat ke pesta. Tapi: Saya akan mengulangi indeks. Tetapi tidak dengan forpengulangan klasik melainkan dengan forpengulangan berbasis rentang di atas indeks:

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

indicesadalah fungsi pembungkus sederhana yang mengembalikan rentang (dievaluasi secara malas) untuk indeks. Karena implementasinya - meskipun sederhana - agak terlalu lama untuk diposting di sini, Anda dapat menemukan implementasinya di GitHub .

Kode ini seefisien menggunakan forloop manual dan klasik .

Jika pola ini sering muncul dalam data Anda, pertimbangkan untuk menggunakan pola lain yang merupakan zipdua urutan dan menghasilkan berbagai tupel, sesuai dengan elemen yang dipasangkan:

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}

Implementasi zipdibiarkan sebagai latihan bagi pembaca, tetapi mengikuti dengan mudah dari implementasi indices.

(Sebelum C ++ 17 Anda harus menulis yang berikut ini :)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);
Konrad Rudolph
sumber
2
Apakah ada keuntungan dari penerapan indeks Anda dibandingkan dengan meningkatkan rentang_hitung? Seseorang dapat dengan mudah menggunakanboost::counting_range(size_t(0), containerA.size())
SebastianK
3
@SebastianK Perbedaan terbesar dalam kasus ini adalah sintaks: milik saya adalah (saya klaim) secara obyektif lebih baik untuk digunakan dalam kasus ini. Selanjutnya, Anda dapat menentukan ukuran langkah. Lihat halaman Github yang ditautkan, dan khususnya file README, sebagai contoh.
Konrad Rudolph
Ide Anda sangat bagus dan saya datang dengan penggunaan count_range hanya setelah melihatnya: clear upvote :) Namun, saya bertanya-tanya apakah ini memberikan nilai tambahan untuk menerapkan (kembali) ini. Misalnya tentang kinerja. Sintaks yang lebih bagus, saya setuju, tentu saja, tetapi itu sudah cukup untuk menulis fungsi generator sederhana untuk mengimbangi kekurangan ini.
SebastianK
@SebastianK Saya mengakui bahwa ketika saya menulis kode, saya menganggapnya cukup sederhana untuk hidup dalam isolasi tanpa menggunakan perpustakaan (dan memang begitu!). Sekarang saya mungkin akan menuliskannya sebagai pembungkus di sekitar Boost.Range. Konon, kinerja perpustakaan saya sudah optimal. Yang saya maksud dengan ini adalah bahwa menggunakan indicesimplementasi saya menghasilkan keluaran kompiler yang identik dengan menggunakan forloop manual . Tidak ada biaya tambahan apapun.
Konrad Rudolph
Karena saya tetap menggunakan boost, itu akan lebih sederhana dalam kasus saya. Saya sudah menulis pembungkus ini di sekitar rentang dorongan: hanya fungsi dengan satu baris kode yang saya butuhkan. Namun, saya akan tertarik jika performa rentang boost juga optimal.
SebastianK
38

Untuk contoh spesifik Anda, gunakan saja

std::copy_n(contB.begin(), contA.size(), contA.begin())

Untuk kasus yang lebih umum, Anda dapat menggunakan Boost.Iterator zip_iterator, dengan fungsi kecil untuk membuatnya dapat digunakan dalam loop berbasis rentang. Untuk kebanyakan kasus, ini akan berhasil:

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
  boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
          boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

// ...
for(auto&& t : zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";

Contoh langsung.

Namun, untuk genericity full-blown, Anda mungkin ingin sesuatu yang lebih seperti ini , yang akan bekerja dengan benar untuk array dan tipe user-defined yang tidak memiliki anggota begin()/ end()tapi jangan memiliki begin/ endfungsi dalam namespace mereka. Selain itu, ini akan memungkinkan pengguna untuk secara khusus mendapatkan constakses melalui zip_c...fungsi.

Dan jika Anda seorang pendukung pesan kesalahan yang bagus, seperti saya, maka Anda mungkin menginginkan ini , yang memeriksa apakah ada wadah sementara yang diteruskan ke salah satu zip_...fungsi, dan mencetak pesan kesalahan yang bagus jika demikian.

Xeo
sumber
1
Terima kasih! Namun ada satu pertanyaan, mengapa Anda menggunakan auto &&, apa artinya &&?
memec
@memecs: Saya sarankan membaca pertanyaan ini , serta jawaban saya ini yang agak menjelaskan bagaimana deduksi dan referensi runtuh dilakukan. Perhatikan bahwa autobekerja persis sama dengan parameter template, dan T&&dalam template adalah referensi universal seperti yang dijelaskan di tautan pertama, jadi auto&& v = 42akan disimpulkan sebagai int&&dan auto&& w = v;kemudian akan disimpulkan sebagai int&. Ini memungkinkan Anda untuk mencocokkan lvalues ​​serta rvalues ​​dan membiarkan keduanya bisa berubah, tanpa membuat salinan.
Xeo
@Xeo: Tapi apa keuntungan dari auto && over auto & in a foreach loop?
Viktor Sehr
@ViktorSehr: Ini memungkinkan Anda mengikat ke elemen sementara, seperti yang dihasilkan oleh zip_range.
Xeo
23
@Xeo Semua tautan ke contoh rusak.
kynan
34

Saya heran mengapa tidak ada yang menyebutkan ini:

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}

PS: jika ukuran wadah tidak cocok, maka Anda harus memasukkan kode di dalam pernyataan if.

Joseph
sumber
9

Ada banyak cara untuk melakukan hal - hal tertentu dengan banyak penampung seperti yang disediakan di algorithmtajuk. Misalnya, dalam contoh yang Anda berikan, Anda dapat menggunakan std::copyfor loop sebagai pengganti eksplisit.

Di sisi lain, tidak ada cara built-in untuk secara umum mengulang beberapa kontainer selain for loop normal. Ini tidak mengherankan karena ada banyak cara untuk mengulang. Pikirkan tentang hal ini: Anda dapat mengulangi satu wadah dengan satu langkah, satu wadah dengan langkah lainnya; atau melalui satu wadah hingga mencapai ujung kemudian mulai memasukkan sementara Anda melewati ke ujung wadah lainnya; atau satu langkah dari wadah pertama untuk setiap kali Anda benar-benar melewati wadah lainnya, lalu mulai lagi; atau pola lainnya; atau lebih dari dua wadah sekaligus; dll ...

Namun, jika Anda ingin membuat fungsi gaya "for_each" Anda sendiri yang melakukan iterasi melalui dua penampung hanya sampai penampung yang paling pendek, Anda dapat melakukan sesuatu seperti ini:

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

Tentunya Anda dapat membuat strategi iterasi apa pun yang Anda inginkan dengan cara yang serupa.

Tentu saja, Anda mungkin berpendapat bahwa melakukan perulangan for dalam secara langsung lebih mudah daripada menulis fungsi khusus seperti ini ... dan Anda akan benar, jika Anda hanya akan melakukannya satu atau dua kali. Tetapi hal yang menyenangkan adalah ini sangat dapat digunakan kembali. =)

wjl
sumber
Sepertinya Anda harus mendeklarasikan iterator sebelum loop? Saya mencoba ini: for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)tetapi penyusun berteriak. Adakah yang bisa menjelaskan mengapa ini tidak valid?
David Doria
@DavidDoria Bagian pertama dari perulangan for adalah pernyataan tunggal. Anda tidak dapat mendeklarasikan dua variabel dengan tipe berbeda dalam pernyataan yang sama. Pikirkan tentang mengapa for (int x = 0, y = 0; ...berhasil, tetapi for (int x = 0, double y = 0; ...)tidak.
wjl
1
.. Anda bisa, bagaimanapun, memiliki std :: pair <Container1 :: iterator, Container2 :: iterator> its = {c1.begin (), c2.begin ()};
lorro
1
Hal lain yang perlu diperhatikan adalah bahwa ini dapat dengan mudah dibuat variadik dengan C ++ 14'stypename...
wjl
8

Jika Anda perlu mengulang secara bersamaan melalui 2 penampung saja, ada versi tambahan dari algoritme for_each standar di pustaka rentang peningkatan, misalnya:

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}

Saat Anda perlu menangani lebih dari 2 kontainer dalam satu algoritme, Anda harus bermain dengan zip.

tsar
sumber
Hebat! Bagaimana Anda menemukannya? Sepertinya itu tidak didokumentasikan di mana pun.
Mikhail
4

solusi lain dapat menangkap referensi dari iterator wadah lain dalam lambda dan menggunakan operator kenaikan pos di atasnya. misalnya salinan sederhana adalah:

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })

di dalam lambda Anda dapat melakukan apa saja dengan itadan kemudian menaikkannya. Ini dengan mudah meluas ke beberapa wadah kontainer.

Vahid
sumber
3

Pustaka rentang menyediakan fungsi ini dan fungsi lain yang sangat membantu. Contoh berikut menggunakan Boost.Range . Rangev3 Eric Niebler seharusnya menjadi alternatif yang baik.

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

C ++ 17 akan membuatnya lebih baik dengan binding terstruktur:

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}
Jens
sumber
Program ini tidak dapat dikompilasi dengan g ++ 4.8.0. delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^
syam
Setelah mengubah std :: tie menjadi boost: tie, itu dikompilasi.
syam
Saya mendapatkan kesalahan kompilasi berikut untuk versi dengan pengikatan terstruktur (menggunakan 19.13.26132.0versi MSVC dan Windows SDK 10.0.16299.0): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)
pooya13
binding terstruktur sepertinya tidak berfungsi dengan boost::combine: stackoverflow.com/q/55585723/8414561
Dev Null
2

Saya sedikit terlambat; tetapi Anda dapat menggunakan ini (fungsi variadic gaya-C):

template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);

atau ini (menggunakan paket parameter fungsi):

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);

atau ini (menggunakan daftar penginisialisasi tertutup kurung kurawal):

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});

atau Anda dapat menggabungkan vektor seperti di sini: Apa cara terbaik untuk menggabungkan dua vektor? dan kemudian melakukan iterasi pada vektor besar.

Szymon Marczak
sumber
0

Ini satu varian

template<class ... Iterator>
void increment_dummy(Iterator ... i)
    {}

template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
    {
    while(N!=0)
        {
        fun(*iter...);
        increment_dummy(++iter...);
        --N;
        }
    }

Contoh penggunaan

void arrays_mix(size_t N,const float* x,const float* y,float* z)
    {
    for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);    
    }
pengguna877329
sumber