ฉันจะหลีกเลี่ยง "for" ลูปที่มีเงื่อนไข "if" ภายในด้วย C ++ ได้อย่างไร


111

ด้วยโค้ดเกือบทั้งหมดที่ฉันเขียนฉันมักจะจัดการกับปัญหาการลดการตั้งค่าในคอลเลกชั่นที่ท้ายที่สุดแล้วมีเงื่อนไข "if" ที่ไร้เดียงสาอยู่ภายใน นี่คือตัวอย่างง่ายๆ:

for(int i=0; i<myCollection.size(); i++)
{
     if (myCollection[i] == SOMETHING)
     {
           DoStuff();
     }
}

ด้วยภาษาที่ใช้งานได้ฉันสามารถแก้ปัญหาได้โดยการลดคอลเล็กชันลงในคอลเล็กชันอื่น (อย่างง่ายดาย) จากนั้นดำเนินการทั้งหมดกับชุดที่ลดลงของฉัน ใน pseudocode:

newCollection <- myCollection where <x=true
map DoStuff newCollection

และในรูปแบบ C อื่น ๆ เช่น C # ฉันสามารถลดด้วย where clause like

foreach (var x in myCollection.Where(c=> c == SOMETHING)) 
{
   DoStuff();
}

หรือดีกว่า (อย่างน้อยก็ในสายตาของฉัน)

myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));

เป็นที่ยอมรับว่าฉันใช้กระบวนทัศน์ผสมผสานและรูปแบบตามอัตนัย / ความคิดเห็นมากมาย แต่ฉันอดไม่ได้ที่จะรู้สึกว่าฉันขาดบางสิ่งพื้นฐานที่ทำให้ฉันใช้เทคนิคที่ต้องการนี้กับ C ++ ได้ ใครช่วยสอนฉันได้ไหม


7
คุณสามารถลองใช้งานไลบรารีมาตรฐาน C ++ ได้std::copy_ifแต่การเลือกไม่น่าเกียจ
milleniumbug

14
คุณอาจจะสนใจในช่วง v3 มันควรจะมาถึง C ++ ในรูปแบบ TS และหวังว่าจะเป็นมาตรฐานในรุ่นต่อ ๆ ไป
NathanOliver

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

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

10
@CodyGray เห็นด้วย: มันเป็นเพียงแค่น้ำตาลสังเคราะห์ และชื่อคำถามนั้นทำให้เข้าใจผิดเพราะมันแตกต่างกันมากที่จะหลีกเลี่ยงการแยกสาขาและซ่อนไว้ภายใต้นามธรรม
edmz

คำตอบ:


99

IMHO ตรงไปตรงมามากขึ้นและอ่านได้ง่ายขึ้นในการใช้ for loop กับ if ภายใน อย่างไรก็ตามหากสิ่งนี้น่ารำคาญสำหรับคุณคุณสามารถใช้สิ่งที่for_each_ifคล้ายกันด้านล่าง:

template<typename Iter, typename Pred, typename Op> 
void for_each_if(Iter first, Iter last, Pred p, Op op) {
  while(first != last) {
    if (p(*first)) op(*first);
    ++first;
  }
}

usecase:

std::vector<int> v {10, 2, 10, 3};
for_each_if(v.begin(), v.end(), [](int i){ return i > 5; }, [](int &i){ ++i; });

การสาธิตสด


10
ที่ฉลาดเป็นพิเศษ ฉันจะยอมรับด้วยว่ามันไม่ได้ตรงไปตรงมาและฉันอาจจะใช้ถ้าเงื่อนไขเมื่อเขียนโปรแกรม C ++ ที่คนอื่นใช้ แต่นั่นคือสิ่งที่ฉันต้องการสำหรับการใช้งานส่วนตัวของฉันเอง! :)
Darkenor

14
@Default การส่งผ่านคู่ตัววนซ้ำแทนที่จะเป็นคอนเทนเนอร์นั้นมีทั้ง C ++ ที่ยืดหยุ่นและเป็นสำนวนมากกว่า
Mark B

8
@Slava โดยทั่วไปช่วงจะไม่ลดจำนวนอัลกอริทึม ตัวอย่างเช่นคุณยังคงต้องการfind_ifและfindไม่ว่าจะทำงานกับช่วงหรือคู่ของตัววนซ้ำ (มีข้อยกเว้นบางประการเช่นfor_eachและfor_each_n) วิธีหลีกเลี่ยงการเขียนอัลกอสใหม่สำหรับการจามทุกครั้งคือการใช้การดำเนินการที่แตกต่างกันกับอัลกอสที่มีอยู่เช่นแทนที่จะfor_each_ifฝังเงื่อนไขลงในสิ่งที่เรียกได้for_eachเช่นfor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Jonathan Wakely

9
ฉันจะได้เห็นด้วยกับประโยคแรก: มาตรฐานสำหรับการแก้ปัญหาคือถ้ามากเพิ่มเติมสามารถอ่านได้และง่ายต่อการทำงานร่วมกับ ฉันคิดว่าไวยากรณ์แลมบ์ดาและการใช้เทมเพลตที่กำหนดไว้ที่อื่นเพียงเพื่อจัดการกับลูปแบบธรรมดาจะทำให้เกิดความรำคาญหรืออาจทำให้นักพัฒนาอื่น ๆ สับสน คุณกำลังเสียสละพื้นที่และประสิทธิภาพเพื่อ ... อะไรนะ? สามารถเขียนบางสิ่งในบรรทัดเดียวได้หรือไม่?
user1354557

45
Cough @Darkenor โดยทั่วไปแล้วควรหลีกเลี่ยงการเขียนโปรแกรมที่ "ฉลาดเป็นพิเศษ " เพราะมันทำให้คนอื่นรำคาญรวมถึงตัวคุณเองในอนาคตด้วย
Ryan

48

Boost มีช่วงที่สามารถใช้ได้โดยอิงตามช่วงสำหรับ ช่วงที่มีความได้เปรียบที่ว่าพวกเขาไม่ได้คัดลอกโครงสร้างข้อมูลพื้นฐานพวกเขาเพียงแค่ให้ 'มุมมอง' (นั่นคือbegin(), end()สำหรับช่วงและoperator++(), operator==()สำหรับ iterator) ที่ สิ่งนี้อาจเป็นที่สนใจของคุณ: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.html

#include <boost/range/adaptor/filtered.hpp>
#include <iostream>
#include <vector>

struct is_even
{
    bool operator()( int x ) const { return x % 2 == 0; }
};

int main(int argc, const char* argv[])
{
    using namespace boost::adaptors;

    std::vector<int> myCollection{1,2,3,4,5,6,7,8,9};

    for( int i: myCollection | filtered( is_even() ) )
    {
        std::cout << i;
    }
}

1
ฉันขอแนะนำให้ใช้ตัวอย่าง OP แทนเช่นis_even=> condition, input=> myCollectionเป็นต้น
ค่าเริ่มต้น

นี่เป็นคำตอบที่ยอดเยี่ยมและแน่นอนว่าฉันต้องการทำอะไร ฉันจะระงับการยอมรับเว้นแต่จะมีใครสามารถหาวิธีที่เป็นไปตามมาตรฐานในการดำเนินการที่ใช้การดำเนินการที่ขี้เกียจ / รอการตัดบัญชี upvoted
Darkenor

5
@Darkenor: หาก Boost เป็นปัญหาสำหรับคุณ (เช่นคุณถูกห้ามใช้เนื่องจากนโยบายของ บริษัท และภูมิปัญญาของผู้จัดการ) ฉันสามารถหาคำจำกัดความที่ง่ายขึ้นfiltered()สำหรับคุณได้ซึ่งกล่าวได้ว่าเป็นการดีกว่าที่จะใช้ lib ที่รองรับมากกว่าโค้ด ad-hoc บางตัว
lorro

เห็นด้วยกับคุณโดยสิ้นเชิง ฉันยอมรับเพราะวิธีที่เป็นไปตามมาตรฐานที่มาก่อนเพราะคำถามนั้นมุ่งเน้นไปที่ C ++ ไม่ใช่ไลบรารีเพิ่ม แต่นี่ยอดเยี่ยมจริงๆ นอกจากนี้ - ใช่ฉันได้ทำงานอย่างน่าเศร้าในหลายสถานที่ที่ห้าม Boost ด้วยเหตุผลที่ไร้เหตุผล ...
Darkenor

@ ลีคลาเกตต์:? .
lorro

44

แทนที่จะสร้างอัลกอริทึมใหม่เหมือนคำตอบที่ยอมรับคุณสามารถใช้อัลกอริทึมที่มีอยู่กับฟังก์ชันที่ใช้เงื่อนไข:

std::for_each(first, last, [](auto&& x){ if (cond(x)) { ... } });

หรือถ้าคุณต้องการอัลกอริทึมใหม่อย่างน้อยก็ใช้ซ้ำfor_eachแทนการทำซ้ำตรรกะการวนซ้ำ:

template<typename Iter, typename Pred, typename Op> 
  void
  for_each_if(Iter first, Iter last, Pred p, Op op) {
    std::for_each(first, last, [&](auto& x) { if (p(x)) op(x); });
  }

ดีกว่าและชัดเจนกว่ามากสำหรับการใช้ไลบรารีมาตรฐาน
ไม่ระบุชื่อ

4
เพราะstd::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });ง่ายกว่าทั้งหมดfor (Iter x = first; x != last; x++) if (p(x)) op(x);}?
user253751

2
@immibis การนำไลบรารีมาตรฐานกลับมาใช้ซ้ำมีประโยชน์อื่น ๆ เช่นการตรวจสอบความถูกต้องของตัววนซ้ำหรือ (ใน C ++ 17) การขนานได้ง่ายกว่ามากเพียงแค่เพิ่มอาร์กิวเมนต์อีกหนึ่งรายการ: std::for_each(std::execution::par, first, last, ...);การเพิ่มสิ่งเหล่านั้นลงในลูปที่เขียนด้วยลายมือนั้นง่ายเพียงใด
Jonathan Wakely

1
#pragma omp parallel สำหรับ
Mark K Cowan

2
@mark ขออภัยความแปลกประหลาดบางอย่างของซอร์สโค้ดหรือบิลด์เชนของคุณทำให้ส่วนขยายคอมไพเลอร์แบบขนานที่ไม่ได้มาตรฐานที่เปราะบางอย่างน่ารำคาญทำให้เพิ่มประสิทธิภาพเป็นศูนย์โดยไม่มีการวินิจฉัย
Yakk - Adam Nevraumont

21

ความคิดในการหลีกเลี่ยง

for(...)
    if(...)

โครงสร้างเป็นแอนติแพตเทิร์นกว้างเกินไป

การประมวลผลหลายรายการที่ตรงกับนิพจน์จากภายในลูปเป็นเรื่องปกติดีและโค้ดจะไม่ชัดเจนไปกว่านั้น หากการประมวลผลมีขนาดใหญ่เกินไปที่จะพอดีกับหน้าจอนั่นเป็นเหตุผลที่ดีที่จะใช้รูทีนย่อย แต่ยังคงวางเงื่อนไขไว้ในลูปได้ดีที่สุดนั่นคือ

for(...)
    if(...)
        do_process(...);

เป็นที่นิยมอย่างมาก

for(...)
    maybe_process(...);

มันจะกลายเป็นแอนติแพตเทิร์นเมื่อมีเพียงองค์ประกอบเดียวเท่านั้นที่จะจับคู่กันเพราะงั้นมันจะชัดเจนกว่าในการค้นหาองค์ประกอบก่อนและดำเนินการประมวลผลนอกลูป

for(int i = 0; i < size; ++i)
    if(i == 5)

เป็นตัวอย่างที่ชัดเจนและชัดเจนในเรื่องนี้ บอบบางมากขึ้นและโดยทั่วไปมากขึ้นคือรูปแบบโรงงานเช่น

for(creator &c : creators)
    if(c.name == requested_name)
    {
        unique_ptr<object> obj = c.create_object();
        obj.owner = this;
        return std::move(obj);
    }

เป็นเรื่องยากที่จะอ่านเนื่องจากไม่ชัดเจนว่ารหัสเนื้อหาจะถูกเรียกใช้เพียงครั้งเดียวเท่านั้น ในกรณีนี้การแยกการค้นหาจะดีกว่า:

creator &lookup(string const &requested_name)
{
    for(creator &c : creators)
        if(c.name == requested_name)
            return c;
}

creator &c = lookup(requested_name);
unique_ptr obj = c.create_object();

ยังคงมีifอยู่ภายใน a forแต่จากบริบทมันชัดเจนว่ามันทำอะไรไม่จำเป็นต้องเปลี่ยนรหัสนี้เว้นแต่การค้นหาจะเปลี่ยนไป (เช่นเป็น a map) และเป็นที่ชัดเจนในทันทีที่create_object()เรียกเพียงครั้งเดียวเนื่องจากเป็น ไม่อยู่ในวง


ฉันชอบสิ่งนี้ในฐานะภาพรวมที่รอบคอบและสมดุลแม้ว่าในแง่หนึ่งจะปฏิเสธที่จะตอบคำถามที่วางไว้ก็ตาม ฉันพบว่าfor( range ){ if( condition ){ action } }- สไตล์ทำให้ง่ายต่อการอ่านสิ่งต่างๆทีละชิ้นและใช้ความรู้เกี่ยวกับโครงสร้างภาษาพื้นฐานเท่านั้น
PJTraill

@PJTraill วิธีที่ประโยคคำถามทำให้ฉันนึกถึงการพูดจาโผงผางของเรย์มอนด์เฉินต่อปฏิปักษ์แบบ for-ifซึ่งได้รับการปลูกฝังการขนส่งสินค้าและกลายเป็นสิ่งที่แน่นอน ฉันยอมรับโดยสิ้นเชิงว่าfor(...) if(...) { ... }มักเป็นตัวเลือกที่ดีที่สุด (นั่นเป็นเหตุผลที่ฉันมีคุณสมบัติตามคำแนะนำเพื่อแบ่งการดำเนินการออกเป็นรูทีนย่อย)
Simon Richter

1
ขอบคุณสำหรับลิงก์ที่ให้ความกระจ่างสำหรับฉัน: ชื่อ " for-if " ทำให้เข้าใจผิดและควรเป็น " for-all-if-one " หรือ " lookup-หลีกเลี่ยง " มันทำให้ฉันนึกถึงวิธีการผกผันของ Abstraction ที่วิกิพีเดียอธิบายไว้ในปี 2548เมื่อมีการ " สร้างโครงสร้างอย่างง่ายบนซับซ้อน (อัน)" - จนกว่าฉันจะเขียนใหม่! จริงๆแล้วฉันจะไม่รีบแก้ไขรูปแบบการค้นหากระบวนการออกfor(…)if(…)…ว่าเป็นการค้นหาที่เดียวหรือไม่
PJTraill

17

นี่คือfilterฟังก์ชั่นที่ค่อนข้างเรียบง่ายอย่างรวดเร็ว

ต้องใช้เพรดิเคต ส่งคืนวัตถุฟังก์ชันที่สามารถทำซ้ำได้

ส่งคืนค่าที่สามารถทำซ้ำได้ซึ่งสามารถใช้แบบfor(:)วนซ้ำได้

template<class It>
struct range_t {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }

template<class It, class F>
struct filter_helper:range_t<It> {
  F f;
  void advance() {
    while(true) {
      (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      if (this->empty())
        return;
      if (f(*this->begin()))
        return;
    }
  }
  filter_helper(range_t<It> r, F fin):
    range_t<It>(r), f(std::move(fin))
  {
      while(true)
      {
          if (this->empty()) return;
          if (f(*this->begin())) return;
          (range_t<It>&)*this = range( std::next(this->begin()), this->end() );
      }
  }
};

template<class It, class F>
struct filter_psuedo_iterator {
  using iterator_category=std::input_iterator_tag;
  filter_helper<It, F>* helper = nullptr;
  bool m_is_end = true;
  bool is_end() const {
    return m_is_end || !helper || helper->empty();
  }

  void operator++() {
    helper->advance();
  }
  typename std::iterator_traits<It>::reference
  operator*() const {
    return *(helper->begin());
  }
  It base() const {
      if (!helper) return {};
      if (is_end()) return helper->end();
      return helper->begin();
  }
  friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    if (lhs.is_end() && rhs.is_end()) return true;
    if (lhs.is_end() || rhs.is_end()) return false;
    return lhs.helper->begin() == rhs.helper->begin();
  }
  friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
    return !(lhs==rhs);
  }
};
template<class It, class F>
struct filter_range:
  private filter_helper<It, F>,
  range_t<filter_psuedo_iterator<It, F>>
{
  using helper=filter_helper<It, F>;
  using range=range_t<filter_psuedo_iterator<It, F>>;

  using range::begin; using range::end; using range::empty;

  filter_range( range_t<It> r, F f ):
    helper{{r}, std::forward<F>(f)},
    range{ {this, false}, {this, true} }
  {}
};

template<class F>
auto filter( F&& f ) {
    return [f=std::forward<F>(f)](auto&& r)
    {
        using std::begin; using std::end;
        using iterator = decltype(begin(r));
        return filter_range<iterator, std::decay_t<decltype(f)>>{
            range(begin(r), end(r)), f
        };
    };
};

ฉันตัดบท ไลบรารีจริงควรสร้างซ้ำจริงไม่ใช่for(:)หลอกที่มีคุณสมบัติเหมาะสมที่ฉันทำ

ณ จุดใช้งานจะมีลักษณะดังนี้:

int main()
{
  std::vector<int> test = {1,2,3,4,5};
  for( auto i: filter([](auto x){return x%2;})( test ) )
    std::cout << i << '\n';
}

ซึ่งค่อนข้างดีและภาพพิมพ์

1
3
5

ตัวอย่างสด

มีข้อเสนอเพิ่มเติมสำหรับ C ++ ที่เรียกว่า Rangesv3 ซึ่งทำสิ่งนี้และอื่น ๆ boostยังมีช่วงตัวกรอง / ตัวทำซ้ำ Boost ยังมีตัวช่วยที่ทำให้การเขียนข้างต้นสั้นลงมาก


15

รูปแบบหนึ่งที่ใช้มากพอที่จะกล่าวถึง แต่ยังไม่ได้กล่าวถึงคือ:

for(int i=0; i<myCollection.size(); i++) {
  if (myCollection[i] != SOMETHING)
    continue;

  DoStuff();
}

ข้อดี:

  • ไม่เปลี่ยนระดับการเยื้องDoStuff();เมื่อความซับซ้อนของเงื่อนไขเพิ่มขึ้น ตามเหตุผลDoStuff();ควรอยู่ที่ระดับบนสุดของforลูปและเป็น
  • ทันทีทำให้มันชัดเจนว่า iterates ห่วงมากกว่าSOMETHINGของคอลเลกชันโดยไม่ต้องมีผู้อ่านที่จะตรวจสอบว่ามีอะไรหลังจากปิด}ของifบล็อก
  • ไม่ต้องใช้ไลบรารีหรือมาโครตัวช่วยหรือฟังก์ชันใด ๆ

ข้อเสีย:

  • continueเช่นเดียวกับคำสั่งควบคุมการไหลอื่น ๆ ถูกนำไปใช้ในทางที่ผิดในรูปแบบที่นำไปสู่รหัสที่ยากต่อการติดตามมากจนบางคนไม่เห็นด้วยกับการใช้งานใด ๆ : มีรูปแบบการเข้ารหัสที่ถูกต้องซึ่งบางคนทำตามที่หลีกเลี่ยงcontinueที่หลีกเลี่ยงbreakนอกเหนือจาก ใน a switchที่หลีกเลี่ยงreturnนอกเหนือจากตอนท้ายของฟังก์ชัน

3
ฉันจะเถียงว่าในforลูปที่วิ่งไปหลายบรรทัดสองบรรทัด "ถ้าไม่ดำเนินการต่อ" นั้นชัดเจนกว่ามีเหตุผลและอ่านได้ ทันทีที่พูดว่า "ข้ามสิ่งนี้ถ้า" หลังจากที่forคำสั่งนั้นอ่านได้ดีและอย่างที่คุณบอกว่าไม่เยื้องด้านการทำงานที่เหลือของลูป อย่างไรก็ตามหากcontinueยิ่งลงไปอีกความชัดเจนบางอย่างจะถูกเสียสละ (กล่าวคือหากการดำเนินการบางอย่างจะดำเนินการก่อนifคำสั่งเสมอ)
ไม่ระบุชื่อ

11
for(auto const &x: myCollection) if(x == something) doStuff();

ดูเหมือน C ++ - forความเข้าใจเฉพาะสำหรับฉัน ถึงคุณ?


ฉันไม่คิดว่าคำหลักอัตโนมัติมีอยู่ก่อน c ++ 11 ดังนั้นฉันจะไม่บอกว่ามันเป็น c ++ คลาสสิกมาก หากฉันอาจถามคำถามที่นี่ในความคิดเห็น "const อัตโนมัติ" จะบอกคอมไพเลอร์ว่าสามารถจัดเรียงองค์ประกอบทั้งหมดใหม่ได้ตามที่ต้องการหรือไม่ บางทีอาจจะง่ายกว่าสำหรับคอมไพเลอร์ในการวางแผนเพื่อหลีกเลี่ยงการแตกกิ่งก้านหากเป็นเช่นนั้น
mathreadler

1
@mathreadler คนยิ่งเลิกกังวลเกี่ยวกับ "c ++ คลาสสิก" ได้เร็วเท่าไหร่ก็ยิ่งดีเท่านั้น C ++ 11 เป็นเหตุการณ์วิวัฒนาการระดับมหภาคสำหรับภาษาและมีอายุ 5 ปีซึ่งควรเป็นขั้นต่ำที่เรามุ่งมั่น อย่างไรก็ตาม OP ติดแท็กนั้นและ C ++ 14 (ดียิ่งขึ้น!) ไม่auto constไม่มีผลต่อคำสั่งการวนซ้ำ หากคุณค้นหาแบบอิงตามระยะforคุณจะเห็นว่าโดยทั่วไปแล้วมันจะทำลูปมาตรฐานจากbegin()ถึงหนึ่งไปยังend()โดยมีการอ้างอิงโดยปริยาย ไม่มีทางที่จะทำลายการรับประกันการสั่งซื้อ (ถ้ามี) ของคอนเทนเนอร์ที่ทำซ้ำ มันคงจะถูกหัวเราะออกมาจากพื้นโลก
underscore_d

1
@mathreadler จริงๆแล้วมันก็มีความหมายที่แตกต่างออกไป สิ่งที่ไม่มีอยู่คือ range-for ... และคุณลักษณะ C ++ 11 อื่น ๆ ที่แตกต่างกัน สิ่งที่ฉันหมายถึงที่นี่คือ range-fors, std::futures, std::functions แม้กระทั่งการปิดแบบไม่ระบุตัวตนเหล่านั้นก็เป็น C ++ ish ในไวยากรณ์ได้เป็นอย่างดี ทุกภาษามีสำนวนของตัวเองและเมื่อรวมเอาคุณลักษณะใหม่เข้าด้วยกันก็จะพยายามทำให้พวกเขาเลียนแบบไวยากรณ์ที่รู้จักกันดีแบบเก่า
bipll

@underscore_d คอมไพเลอร์ได้รับอนุญาตให้ทำการแปลงใด ๆ หากปฏิบัติตามกฎ as-if ใช่หรือไม่
bipll

1
อืมและมันอาจจะหมายถึงอะไร?
bipll

7

หาก DoStuff () จะขึ้นอยู่กับฉันในอนาคตฉันจะเสนอตัวแปรการมาสก์บิตที่ไม่มีสาขาที่รับประกันนี้

unsigned int times = 0;
const int kSize = sizeof(unsigned int)*8;
for(int i = 0; i < myCollection.size()/kSize; i++){
  unsigned int mask = 0;
  for (int j = 0; j<kSize; j++){
    mask |= (myCollection[i*kSize+j]==SOMETHING) << j;
  }
  times+=popcount(mask);
}

for(int i=0;i<times;i++)
   DoStuff();

โดยที่ popcount คือฟังก์ชันใด ๆ ที่ทำการนับประชากร (นับจำนวนบิต = 1) จะมีอิสระในการกำหนดข้อ จำกัด ขั้นสูงกับฉันและเพื่อนบ้านของพวกเขา หากไม่ต้องการเราสามารถถอดห่วงด้านในและสร้างห่วงด้านนอกใหม่ได้

for(int i = 0; i < myCollection.size(); i++)
  times += (myCollection[i]==SOMETHING);

ตามด้วย

for(int i=0;i<times;i++)
   DoStuff();

6

นอกจากนี้หากคุณไม่สนใจที่จะจัดลำดับคอลเล็กชันใหม่ std :: partition มีราคาถูก

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

void DoStuff(int i)
{
    std::cout << i << '\n';
}

int main()
{
    using namespace std::placeholders;

    std::vector<int> v {1, 2, 5, 0, 9, 5, 5};
    const int SOMETHING = 5;

    std::for_each(v.begin(),
                  std::partition(v.begin(), v.end(),
                                 std::bind(std::equal_to<int> {}, _1, SOMETHING)), // some condition
                  DoStuff); // action
}

แต่std::partitionจัดลำดับคอนเทนเนอร์ใหม่
celtschk

5

ฉันรู้สึกกลัวกับความซับซ้อนของวิธีแก้ปัญหาข้างต้น ฉันกำลังจะไปแนะนำที่เรียบง่าย#define foreach(a,b,c,d) for(a; b; c)if(d)แต่มีการขาดดุลที่เห็นได้ชัดไม่กี่ตัวอย่างเช่นคุณต้องจำไว้ให้ใช้เครื่องหมายจุลภาคแทนอัฒภาคในวงของคุณและคุณไม่สามารถใช้ประกอบการจุลภาคในหรือac

#include <list>
#include <iostream>

using namespace std; 

#define foreach(a,b,c,d) for(a; b; c)if(d)

int main(){
  list<int> a;

  for(int i=0; i<10; i++)
    a.push_back(i);

  for(auto i=a.begin(); i!=a.end(); i++)
    if((*i)&1)
      cout << *i << ' ';
  cout << endl;

  foreach(auto i=a.begin(), i!=a.end(), i++, (*i)&1)
    cout << *i << ' ';
  cout << endl;

  return 0;
}

3
ความซับซ้อนของคำตอบบางคำตอบนั้นสูงเพียงเพราะก่อนอื่นแสดงวิธีการทั่วไปที่ใช้ซ้ำได้ (ซึ่งคุณจะทำเพียงครั้งเดียว) จากนั้นจึงใช้คำตอบนั้น ไม่ได้ผลหากคุณมีหนึ่งลูปที่มีเงื่อนไข if ในแอปพลิเคชันทั้งหมดของคุณ แต่จะมีประสิทธิภาพมากหากเกิดขึ้นเป็นพันครั้ง
gnasher729

1
เช่นเดียวกับคำแนะนำส่วนใหญ่สิ่งนี้ทำให้ยากขึ้นไม่ใช่เรื่องง่ายในการระบุช่วงและเงื่อนไขการเลือก และการใช้มาโครจะเพิ่มความไม่แน่นอนว่าเมื่อใด (และบ่อยเพียงใด) นิพจน์ได้รับการประเมินแม้ว่าจะไม่มีเรื่องน่าประหลาดใจก็ตาม
PJTraill

2

อีกวิธีหนึ่งในกรณีที่ i: s มีความสำคัญ สิ่งนี้สร้างรายการที่เติมในดัชนีที่จะเรียก doStuff () สำหรับ อีกครั้งประเด็นหลักคือการหลีกเลี่ยงการแตกกิ่งก้านและแลกเปลี่ยนเป็นค่าใช้จ่ายทางคณิตศาสตร์แบบไปป์ไลน์

int buffer[someSafeSize];
int cnt = 0; // counter to keep track where we are in list.
for( int i = 0; i < container.size(); i++ ){
   int lDecision = (container[i] == SOMETHING);
   buffer[cnt] = lDecision*i + (1-lDecision)*buffer[cnt];
   cnt += lDecision;
}

for( int i=0; i<cnt; i++ )
   doStuff(buffer[i]); // now we could pass the index or a pointer as an argument.

บรรทัด "ขลัง" คือบรรทัดโหลดบัฟเฟอร์ที่คำนวณทางคณิตศาสตร์เพื่อรักษามูลค่าและอยู่ในตำแหน่งหรือเพื่อนับตำแหน่งและเพิ่มมูลค่า ดังนั้นเราจึงแลกเปลี่ยนสาขาที่เป็นไปได้สำหรับลอจิกและเลขคณิตและอาจจะเป็นแคชฮิต สถานการณ์ทั่วไปที่จะเป็นประโยชน์คือถ้า doStuff () ทำการคำนวณไปป์ไลน์จำนวนเล็กน้อยและสาขาใด ๆ ที่อยู่ระหว่างการโทรอาจขัดจังหวะไปป์ไลน์เหล่านั้นได้

จากนั้นวนรอบบัฟเฟอร์แล้วรัน doStuff () จนกว่าเราจะถึง cnt คราวนี้เราจะมี i ปัจจุบันเก็บไว้ในบัฟเฟอร์เพื่อให้เราสามารถใช้ในการเรียก doStuff () ได้หากเราต้องการ


1

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

นี้จะทำได้ในลักษณะที่ตรงไปตรงมามากที่สุดกับเอริค Neibler ของห้องสมุดช่วง-v3 ; แม้ว่าจะดูน่าเบื่อเล็กน้อยเพราะคุณต้องการทำงานกับดัชนี:

using namespace ranges;
auto mycollection_has_something = 
    [&](std::size_t i) { return myCollection[i] == SOMETHING };
auto filtered_view = 
    views::iota(std::size_t{0}, myCollection.size()) | 
    views::filter(mycollection_has_something);
for (auto i : filtered_view) { DoStuff(); }

แต่ถ้าคุณยินดีที่จะละทิ้งดัชนีคุณจะได้รับ:

auto is_something = [&SOMETHING](const decltype(SOMETHING)& x) { return x == SOMETHING };
auto filtered_collection = myCollection | views::filter(is_something);
for (const auto& x : filtered_collection) { DoStuff(); }

ซึ่งดีกว่า IMHO

PS - ไลบรารี range ส่วนใหญ่จะเข้าสู่มาตรฐาน C ++ ใน C ++ 20


0

ฉันจะพูดถึง Mike Acton แน่นอนเขาจะพูดว่า:

หากคุณต้องทำเช่นนั้นแสดงว่าข้อมูลของคุณมีปัญหา จัดเรียงข้อมูลของคุณ!

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