Aynı anda iki veya daha fazla konteyneri yinelemenin en iyi yolu nedir?


114

C ++ 11, kapsayıcılar üzerinde yineleme yapmak için birden çok yol sağlar. Örneğin:

Menzile dayalı döngü

for(auto c : container) fun(c)

std :: for_each

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

Bununla birlikte, aşağıdakiler gibi bir şeyi gerçekleştirmek için aynı boyutta iki (veya daha fazla) kabı yinelemek için önerilen yol nedir:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}

1
transformiçinde mevcut olan ne olacak #include <algorithm>?
Ankit Acharya

Atama döngüsü hakkında: Her ikisi de vektör veya benzerse, containerA = containerB;döngü yerine kullanın .
emlai


Yanıtlar:


53

Partiye oldukça geç. Ancak: Endeksler üzerinde yineleme yapardım. Ancak klasik fordöngü ile değil, bunun yerine forendeksler üzerinde aralık tabanlı bir döngü ile:

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

indicesendeksler için (tembel olarak değerlendirilen) bir aralık döndüren basit bir sarmalayıcı işlevidir. Uygulama - basit olsa da - burada yayınlamak için biraz fazla uzun olduğu için GitHub'da bir uygulama bulabilirsiniz .

Bu kod, manuel, klasik bir döngü kullanmak kadar etkilidirfor .

Bu model verilerinizde sık sık ortaya çıkıyorsa, zipiki dizi içeren ve eşleştirilmiş öğelere karşılık gelen bir dizi tuple üreten başka bir model kullanmayı düşünün :

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

Uygulaması zipokuyucu için bir alıştırma olarak bırakılır, ancak uygulamasından kolayca takip edilir indices.

(C ++ 17'den önce bunun yerine aşağıdakileri yazmanız gerekir :)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);

2
Endeks uygulamanızın sayma_aralığını artırmaya kıyasla herhangi bir avantajı var mı? Basitçe kullanılabilirboost::counting_range(size_t(0), containerA.size())
SebastianK

3
@SebastianK Bu durumda en büyük fark sözdizimidir: benimki (iddia ediyorum) bu durumda kullanmak için nesnel olarak daha iyi. Ayrıca, bir adım boyutu da belirleyebilirsiniz. Örnekler için bağlantılı Github sayfasına ve özellikle README dosyasına bakın.
Konrad Rudolph

Fikriniz çok güzel ve counting_range kullanımını ancak gördükten sonra buldum: açık oyla :) Ancak, bunu (yeniden) uygulamak için ek bir değer sağlayıp sağlamadığını merak ediyorum. Örneğin, performansla ilgili. Daha güzel sözdizimi, elbette katılıyorum, ancak bu dezavantajı telafi etmek için basit bir jeneratör işlevi yazmak yeterli olacaktır.
SebastianK

@SebastianK Kodu yazarken bir kütüphane kullanmadan izole bir şekilde yaşamayı yeterince basit bulduğumu kabul ediyorum (ve öyle!). Şimdi muhtemelen Boost.Range etrafına bir paketleyici olarak yazardım. Bununla birlikte, kütüphanemin performansı zaten optimal. Bununla kastettiğim, benim indicesuygulamamı kullanmanın, manuel döngüleri kullanmakla aynı olan derleyici çıktısını forvermesidir. Herhangi bir ek yük yoktur.
Konrad Rudolph

Yine de boost kullandığım için, benim durumumda daha basit olurdu. Bu sarmalayıcıyı artırma aralığına zaten yazmıştım: tek ihtiyacım olan tek satır kodlu bir işlev. Bununla birlikte, yükseltme aralıklarının performansı da optimalse ilgilenirim.
SebastianK

38

Size özel örnek için şunu kullanın:

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

Daha genel bir durum için, Boost.Iterator's'ı zip_iterator, döngüler için aralık tabanlı olarak kullanılabilir hale getirmek için küçük bir işlevle kullanabilirsiniz. Çoğu durumda bu işe yarar:

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";

Canlı örnek.

Ancak, tam gelişmiş genericity için, muhtemelen daha fazla gibi bir şey istiyorum bu üyeyi yok diziler ve kullanıcı tanımlı türleri için doğru şekilde çalışır, begin()/ end()ama do var begin/ endonların ad fonksiyonlarını. Ayrıca, bu, kullanıcının özellikle işlevler constaracılığıyla erişmesine izin verecektir zip_c....

Ve benim gibi güzel hata mesajlarının savunucusuysanız, muhtemelen bunu istersiniz, bu , zip_...işlevlerden herhangi birine herhangi bir geçici kapsayıcı geçirilip geçirilmediğini kontrol eder ve öyleyse güzel bir hata mesajı yazdırır.


1
Teşekkürler! Yine de bir soru, neden auto && kullanıyorsunuz, && ne anlama geliyor?
memecs

@memecs: Bu soruyu okumanızı tavsiye ederim , ayrıca bu cevabım kesinti ve referans çökmesinin nasıl yapıldığını açıklıyor. Not autotam bir şablon parametresi olarak aynı şekildedir ve T&&bir şablonda, bu nedenle ilk bağlantının açıklandığı gibi evrensel bir referanstır auto&& v = 42olarak çıkarılabilir edilecek int&&ve auto&& w = v;sonra da çıkarılabilir edilecektir int&. Bir kopya oluşturmadan ldeğerleri ve rdeğerleri eşleştirmenize ve her ikisinin de değiştirilebilir olmasına izin verir.
Xeo

@Xeo: Peki ya otomatik & & &'nin otomobile ve foreach döngüsüne göre avantajı nedir?
Viktor Sehr

@ViktorSehr: Üretilenler gibi geçici öğelere bağlanmanıza izin verir zip_range.
Xeo

23
@Xeo Örneklere giden tüm bağlantılar kopmuştur.
kynan

34

Merak ediyorum neden kimse bundan bahsetmedi:

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;
    }
}

Not: kapsayıcı boyutları eşleşmiyorsa, kodu if ifadelerinin içine koymanız gerekecektir.


9

Başlıkta sağlandığı gibi, birden çok kapsayıcıyla belirli şeyler yapmanın birçok yolu vardır algorithm. Örneğin, verdiğiniz örnekte, std::copyaçık bir for döngüsü yerine kullanabilirsiniz .

Öte yandan, normal bir for döngüsü dışında birden çok kabı genel olarak yinelemenin yerleşik bir yolu yoktur. Bu şaşırtıcı değil çünkü yinelemenin birçok yolu var. Bir düşünün: bir kapta bir adımda, bir kapta başka bir adımda yineleme yapabilirsiniz; veya bir kaptan sonuna gelene kadar, ardından diğer kabın sonuna geçerken yerleştirmeye başlayın; veya diğer konteynırdan her tam olarak geçtiğinizde ilk konteynerin bir adımı ve ardından baştan başlayın; veya başka bir model; veya bir seferde ikiden fazla kap; vb ...

Ancak, yalnızca en kısa olanın uzunluğuna kadar iki kapsayıcıda yinelenen kendi "for_each" stil işlevinizi yapmak istiyorsanız, şöyle bir şey yapabilirsiniz:

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);
  }
}

Açıkçası, istediğiniz her türlü yineleme stratejisini benzer şekilde yapabilirsiniz.

Tabii ki, sadece iç for döngüsünü doğrudan yapmanın, bunun gibi özel bir işlev yazmaktan daha kolay olduğunu iddia edebilirsiniz ... ve bunu yalnızca bir veya iki kez yapacaksanız haklı olursunuz. Ama güzel olan şey, bunun yeniden kullanılabilir olması. =)


Döngüden önce yineleyicileri bildirmeniz gerekiyor gibi görünüyor? Bunu denedim: for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)ama derleyici bağırıyor. Bunun neden geçersiz olduğunu kimse açıklayabilir mi?
David Doria

@DavidDoria For döngüsünün ilk bölümü tek bir ifadedir. Aynı ifadede farklı türlerde iki değişken tanımlayamazsınız. Neden for (int x = 0, y = 0; ...işe yaradığını ama for (int x = 0, double y = 0; ...)yaramadığını düşünün .
wjl

1
.. ancak std :: pair <Container1 :: iterator, Container2 :: iterator> its = {c1.begin (), c2.begin ()} olabilir;
lorro

1
Unutulmaması gereken bir diğer nokta da, bunun C ++ typename...
14'lerle

8

Yalnızca 2 kapsayıcı üzerinde aynı anda yinelemeniz gerektiğinde, artırma aralığı kitaplığında her biri için standart algoritmanın genişletilmiş bir sürümü vardır, örneğin:

#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;
}

Bir algoritmada 2'den fazla konteyneri işlemeniz gerektiğinde, zip ile oynamanız gerekir.


Olağanüstü! Nasıl buldun? Hiçbir yerde belgelenmemiş gibi görünüyor.
Mikhail

4

başka bir çözüm, bir lambda'da diğer kabın yineleyicisinin bir referansını yakalamak ve bunun üzerinde artım sonrası operatörü kullanmak olabilir. örneğin basit bir kopya şöyle olacaktır:

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++; })

lambda içinde her şeyi yapabilir itave sonra onu artırabilirsiniz . Bu, birden fazla konteyner kasasına kolayca uzanır.


3

Bir dizi kitaplığı bunu ve diğer çok yararlı işlevleri sağlar. Aşağıdaki örnek Boost.Range kullanır . Eric Niebler'in rangev3'ü iyi bir alternatif olmalı.

#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, yapılandırılmış bağlamalarla bunu daha da iyi hale getirecek:

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;
}

Bu program g ++ 4.8.0 ile derlenmiyor. 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

Std :: tie to boost: tie değiştirildikten sonra derlendi.
syam

Yapılandırılmış bağlama içeren sürüm için aşağıdaki derleme hatasını alıyorum (MSVC 19.13.26132.0ve Windows SDK sürümünü kullanarak 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

yapılandırılmış bağlamalar şunlarla çalışmıyor gibi görünüyor boost::combine: stackoverflow.com/q/55585723/8414561
Dev Null

2

Bende biraz geç kaldım ancak bunu kullanabilirsiniz (C tarzı değişken işlev):

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);

veya bu (bir fonksiyon parametre paketi kullanarak):

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);

veya bu (küme ayracı içeren başlatıcı listesi kullanarak):

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});

veya aşağıdaki gibi vektörlere katılabilirsin: İki vektörü birleştirmenin en iyi yolu nedir? ve sonra büyük vektör üzerinde yineleyin.


0

İşte bir varyant

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;
        }
    }

Örnek kullanım

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);    
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.