อะไรคือวิธีที่ดีที่สุดในการทำซ้ำสองคอนเทนเนอร์ขึ้นไปพร้อมกัน


114

C ++ 11 มีหลายวิธีในการวนซ้ำบนคอนเทนเนอร์ ตัวอย่างเช่น:

ลูปตามช่วง

for(auto c : container) fun(c)

มาตรฐาน :: for_each

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

อย่างไรก็ตามวิธีที่แนะนำในการทำซ้ำสองคอนเทนเนอร์ (หรือมากกว่า) ที่มีขนาดเท่ากันเพื่อทำสิ่งต่างๆเช่น:

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

1
แล้วtransformของขวัญในปัจจุบัน#include <algorithm>ล่ะ?
Ankit Acharya

เกี่ยวกับลูปการกำหนด: ถ้าทั้งสองเป็นเวกเตอร์หรือคล้ายกันให้ใช้containerA = containerB;แทนลูป
emlai

คำถามที่คล้ายกัน: stackoverflow.com/questions/8511035/…
knedlsepp

คำตอบ:


53

ค่อนข้างสายไปงานปาร์ตี้ แต่: ฉันจะทำซ้ำในดัชนี แต่ไม่ใช่กับforลูปแบบคลาสสิกแต่ใช้forลูปตามช่วงเหนือดัชนีแทน:

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

indicesเป็นฟังก์ชัน Wrapper อย่างง่ายที่ส่งกลับช่วง (ประเมินอย่างเฉื่อยชา) สำหรับดัชนี เนื่องจากการดำเนินการ - แม้ว่าง่าย - เป็นบิตนานเกินไปที่จะโพสต์ไว้ที่นี่คุณสามารถหาการดำเนินการบน GitHub

รหัสนี้มีประสิทธิภาพเทียบเท่ากับการใช้forลูปแบบคลาสสิกด้วยตนเอง

หากรูปแบบนี้เกิดขึ้นบ่อยครั้งในข้อมูลของคุณให้พิจารณาใช้รูปแบบอื่นซึ่งzipมีสองลำดับและสร้างช่วงของสิ่งที่สอดคล้องกับองค์ประกอบที่จับคู่:

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

การดำเนินการzipเป็นซ้ายเป็นออกกำลังกายสำหรับผู้อ่าน indicesแต่มันดังต่อไปได้อย่างง่ายดายจากการดำเนินงานของ

(ก่อน C ++ 17 คุณต้องเขียนสิ่งต่อไปนี้แทน :)

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

2
มีข้อได้เปรียบของการใช้ดัชนีของคุณเมื่อเทียบกับการเพิ่มช่วง count_range หรือไม่? ใคร ๆ ก็ใช้ได้boost::counting_range(size_t(0), containerA.size())
SebastianK

3
@SebastianK ข้อแตกต่างที่ใหญ่ที่สุดในกรณีนี้คือไวยากรณ์: ของฉันคือ (ฉันอ้างว่า) ดีกว่าที่จะใช้ในกรณีนี้ นอกจากนี้คุณสามารถระบุขนาดขั้นตอน ดูหน้า Github ที่เชื่อมโยงและโดยเฉพาะอย่างยิ่งไฟล์ README สำหรับตัวอย่าง
Konrad Rudolph

ความคิดของคุณดีมากและฉันได้ใช้ count_range หลังจากเห็นมันเท่านั้น: clear upvote :) อย่างไรก็ตามฉันสงสัยว่ามันให้คุณค่าเพิ่มเติมสำหรับ (re-) ใช้สิ่งนี้หรือไม่ เช่นเกี่ยวกับประสิทธิภาพ แน่นอนว่าฉันเห็นด้วยไวยากรณ์ที่ดีกว่า แต่ก็เพียงพอแล้วที่จะเขียนฟังก์ชันตัวสร้างอย่างง่ายเพื่อชดเชยข้อเสียเปรียบนี้
SebastianK

@SebastianK ฉันยอมรับว่าตอนที่ฉันเขียนโค้ดฉันคิดว่ามันง่ายพอที่จะอยู่อย่างโดดเดี่ยวโดยไม่ต้องใช้ห้องสมุด (และมันก็เป็น!) ตอนนี้ฉันอาจจะเขียนเป็น wrapper รอบ ๆ Boost.Range กล่าวได้ว่าประสิทธิภาพของห้องสมุดของฉันดีที่สุดอยู่แล้ว สิ่งที่ฉันหมายถึงคือการใช้การใช้indicesงานของฉันให้ผลลัพธ์ของคอมไพเลอร์ซึ่งเหมือนกับการใช้forลูปด้วยตนเอง ไม่มีค่าใช้จ่ายใด ๆ ทั้งสิ้น
Konrad Rudolph

เนื่องจากฉันใช้บูสต์อยู่แล้วมันจะง่ายกว่าในกรณีของฉัน ฉันเขียนเสื้อคลุมนี้รอบ ๆ ช่วงบูสต์แล้ว: ฟังก์ชั่นที่มีโค้ดหนึ่งบรรทัดคือสิ่งที่ฉันต้องการ อย่างไรก็ตามฉันจะสนใจว่าประสิทธิภาพของช่วงบูสต์นั้นเหมาะสมหรือไม่
SebastianK

38

สำหรับตัวอย่างเฉพาะของคุณเพียงใช้

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

สำหรับกรณีทั่วไปคุณสามารถใช้ Boost.Iterator zip_iteratorซึ่งมีฟังก์ชั่นเล็ก ๆ เพื่อให้สามารถใช้งานได้ตามช่วงสำหรับลูป สำหรับกรณีส่วนใหญ่สิ่งนี้จะได้ผล:

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

ตัวอย่างสด

อย่างไรก็ตามสำหรับ genericity เต็มเป่าคุณอาจต้องการบางสิ่งบางอย่างมากขึ้นเช่นนี้ซึ่งจะทำงานอย่างถูกต้องสำหรับอาร์เรย์และประเภทที่ผู้ใช้กำหนดที่ไม่ได้มีสมาชิกbegin()/ end()แต่ไม่ได้begin/ endฟังก์ชั่นใน namespace ของพวกเขา นอกจากนี้สิ่งนี้จะช่วยให้ผู้ใช้สามารถconstเข้าถึงzip_c...ฟังก์ชันต่างๆได้โดยเฉพาะ

และหากคุณเป็นผู้สนับสนุนข้อความแสดงข้อผิดพลาดที่ดีเช่นฉันคุณอาจต้องการสิ่งนี้ซึ่งตรวจสอบว่าคอนเทนเนอร์ชั่วคราวใด ๆ ถูกส่งผ่านไปยังzip_...ฟังก์ชันใด ๆหรือไม่และพิมพ์ข้อความแสดงข้อผิดพลาดที่ดีหากเป็นเช่นนั้น


1
ขอบคุณ! คำถามหนึ่งว่าทำไมคุณใช้ auto && มันหมายถึงอะไร &&?
memecs

@memecs: ฉันขอแนะนำให้อ่านคำถามนี้เช่นเดียวกับคำตอบของฉันซึ่งอธิบายถึงวิธีการหักและการยุบตัวอ้างอิง โปรดทราบว่าautoการทำงานตรงเช่นเดียวกับพารามิเตอร์เทมเพลตและT&&ในแม่แบบคือการอ้างอิงสากลที่อธิบายไว้ในลิงค์แรกจึงauto&& v = 42จะสรุปได้ว่าเป็นint&&และจากนั้นจะสรุปได้ว่าเป็นauto&& w = v; int&ช่วยให้คุณสามารถจับคู่ค่า lvalues ​​เช่นเดียวกับค่า rvalues ​​และปล่อยให้ทั้งคู่ไม่แน่นอนโดยไม่ต้องทำสำเนา
Xeo

@Xeo: แต่ข้อดีของ auto && มากกว่า auto & ใน foreach loop คืออะไร?
Viktor Sehr

@ViktorSehr: zip_rangeมันช่วยให้คุณสามารถเชื่อมโยงกับองค์ประกอบชั่วคราวเช่นเดียวกับที่ผลิตโดย
Xeo

23
@Xeo ลิงก์ไปยังตัวอย่างทั้งหมดเสีย
kynan

34

ฉันสงสัยว่าทำไมไม่มีใครพูดถึงสิ่งนี้:

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: หากขนาดคอนเทนเนอร์ไม่ตรงกันคุณจะต้องใส่รหัสไว้ในคำสั่ง if


9

มีหลายวิธีในการทำสิ่งที่เฉพาะเจาะจงกับคอนเทนเนอร์หลายรายการตามที่ระบุไว้ในalgorithmส่วนหัว ตัวอย่างเช่นในตัวอย่างที่คุณให้ไว้คุณสามารถใช้std::copyแทน Explicit for loop ได้

ในทางกลับกันไม่มีวิธีในตัวใด ๆ ในการวนซ้ำหลาย ๆ คอนเทนเนอร์นอกเหนือจากปกติสำหรับการวนซ้ำ ไม่น่าแปลกใจเพราะมีหลายวิธีในการทำซ้ำ ลองคิดดู: คุณสามารถวนซ้ำผ่านคอนเทนเนอร์หนึ่งด้วยขั้นตอนเดียวกับอีกขั้นตอนหนึ่ง หรือผ่านภาชนะหนึ่งจนกว่าจะถึงจุดสิ้นสุดจากนั้นเริ่มใส่ในขณะที่คุณผ่านไปยังส่วนท้ายของภาชนะอื่น หรือขั้นตอนหนึ่งของคอนเทนเนอร์แรกทุกครั้งที่คุณผ่านคอนเทนเนอร์อื่นจนสุดแล้วเริ่มต้นใหม่ หรือรูปแบบอื่น ๆ หรือมากกว่าสองตู้ต่อครั้ง ฯลฯ ...

อย่างไรก็ตามหากคุณต้องการสร้างฟังก์ชันสไตล์ "for_each" ของคุณเองซึ่งวนซ้ำผ่านคอนเทนเนอร์สองคอนเทนเนอร์จนถึงความยาวที่สั้นที่สุดเท่านั้นคุณสามารถทำสิ่งนี้ได้:

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

เห็นได้ชัดว่าคุณสามารถสร้างกลยุทธ์การทำซ้ำแบบใดก็ได้ที่คุณต้องการในลักษณะเดียวกัน

แน่นอนคุณอาจโต้แย้งว่าการทำ inner for loop โดยตรงนั้นง่ายกว่าการเขียนฟังก์ชันที่กำหนดเองเช่นนี้ ... และคุณคิดถูกถ้าคุณจะทำเพียงหนึ่งหรือสองครั้ง แต่สิ่งที่ดีคือสามารถใช้ซ้ำได้มาก =)


ดูเหมือนว่าคุณต้องประกาศตัววนซ้ำก่อนลูป? ฉันลองสิ่งนี้for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)แต่คอมไพเลอร์ตะโกน ใครสามารถอธิบายได้ว่าเหตุใดจึงไม่ถูกต้อง?
David Doria

@DavidDoria ส่วนแรกของ for loop คือคำสั่งเดียว คุณไม่สามารถประกาศตัวแปรสองประเภทที่แตกต่างกันในคำสั่งเดียวกัน คิดว่าทำไมถึงใช้for (int x = 0, y = 0; ...งานได้ แต่for (int x = 0, double y = 0; ...)ทำไม่ได้
wjl

1
.. อย่างไรก็ตามคุณสามารถมี std :: pair <Container1 :: iterator, Container2 :: iterator> its = {c1.begin (), c2.begin ()};
lorro

1
สิ่งที่ควรทราบอีกประการหนึ่งคือสามารถสร้างtypename...
ตัวแปร

8

ในกรณีที่คุณต้องการทำซ้ำพร้อมกันบน 2 คอนเทนเนอร์เท่านั้นจะมีอัลกอริทึม for_each มาตรฐานเวอร์ชันเพิ่มเติมในไลบรารีช่วงบูสต์เช่น:

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

เมื่อคุณต้องการจัดการคอนเทนเนอร์มากกว่า 2 ตู้ในอัลกอริทึมเดียวคุณต้องเล่นกับ zip


วิเศษมาก! คุณพบได้อย่างไร? ดูเหมือนว่าจะไม่มีการบันทึกไว้ที่ใดเลย
Mikhail

4

วิธีแก้ปัญหาอื่นอาจจับการอ้างอิงของตัววนซ้ำของคอนเทนเนอร์อื่นในแลมบ์ดาและใช้ตัวดำเนินการเพิ่มโพสต์บนนั้น ตัวอย่างง่ายๆคือ:

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

ภายในแลมบ์ดาคุณสามารถทำอะไรก็ได้itaแล้วเพิ่มขึ้น สิ่งนี้สามารถขยายไปยังตู้คอนเทนเนอร์หลายตู้ได้อย่างง่ายดาย


3

range-library มีฟังก์ชันนี้และฟังก์ชันที่เป็นประโยชน์อื่น ๆ ตัวอย่างต่อไปนี้ใช้Boost.Range rangev3 ของ Eric Niebler น่าจะเป็นทางเลือกที่ดี

#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 จะทำให้สิ่งนี้ดียิ่งขึ้นด้วยการผูกแบบมีโครงสร้าง:

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

โปรแกรมนี้ไม่ได้คอมไพล์ด้วย 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

หลังจากเปลี่ยน std :: tie เป็น boost: tie มันจะคอมไพล์
syam

ฉันได้รับข้อผิดพลาดในการคอมไพล์ต่อไปนี้สำหรับเวอร์ชันที่มีการเชื่อมโยงแบบมีโครงสร้าง (โดยใช้19.13.26132.0เวอร์ชันMSVC และ 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

การเชื่อมโยงแบบมีโครงสร้างดูเหมือนจะใช้ไม่ได้กับboost::combine: stackoverflow.com/q/55585723/8414561
Dev Null

2

ฉันก็สายเกินไป แต่คุณสามารถใช้สิ่งนี้ได้ (ฟังก์ชันตัวแปรสไตล์ 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);

หรือสิ่งนี้ (โดยใช้ชุดพารามิเตอร์ฟังก์ชัน):

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

หรือสิ่งนี้ (โดยใช้รายการเริ่มต้นที่มีวงเล็บปีกกา):

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

หรือคุณสามารถรวมเวกเตอร์ได้ดังนี้อะไรคือวิธีที่ดีที่สุดในการต่อเวกเตอร์สองตัว แล้วทำซ้ำบนเวกเตอร์ขนาดใหญ่


0

นี่คือหนึ่งตัวแปร

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

ตัวอย่างการใช้งาน

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);    
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.