Làm cách nào để tránh các vòng lặp “for” với điều kiện “if” bên trong chúng bằng C ++?


111

Với hầu hết tất cả các mã tôi viết, tôi thường giải quyết các vấn đề giảm tập hợp trên các bộ sưu tập mà cuối cùng kết thúc với các điều kiện "nếu" ngây thơ bên trong chúng. Đây là một ví dụ đơn giản:

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

Với các ngôn ngữ chức năng, tôi có thể giải quyết vấn đề bằng cách giảm tập hợp thành một tập hợp khác (dễ dàng) và sau đó thực hiện tất cả các thao tác trên tập hợp đã giảm của mình. Trong mã giả:

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

Và trong các biến thể C khác, như C #, tôi có thể giảm bằng mệnh đề where như

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

Hoặc tốt hơn (ít nhất là đối với mắt tôi)

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

Phải thừa nhận rằng tôi đang thực hiện rất nhiều sự pha trộn mô hình và phong cách dựa trên chủ quan / ý kiến, nhưng tôi không thể không cảm thấy rằng tôi đang thiếu một thứ thực sự cơ bản có thể cho phép tôi sử dụng kỹ thuật ưa thích này với C ++. Ai đó có thể khai sáng cho tôi?


7
Trong số chức năng thư viện chuẩn C ++, bạn có thể thử std::copy_if, nhưng các lựa chọn không phải là lười biếng
milleniumbug

14
Bạn có thể quan tâm đến range-v3 . Nó cũng sẽ đến với C ++ như một TS và hy vọng sẽ được chuẩn hóa trong một bản phát hành trong tương lai.
NathanOliver

12
Tôi cảm thấy cần phải chỉ ra rằng ifbên trong formà bạn đề cập không chỉ tương đương nhiều về mặt chức năng với các ví dụ khác mà còn có thể nhanh hơn trong nhiều trường hợp. Ngoài ra đối với những người tuyên bố thích phong cách chức năng, những gì bạn đang quảng cáo dường như đi ngược lại với khái niệm thuần khiết được yêu thích của lập trình chức năng vì DoStuffrõ ràng có những tác động phụ.
Pharap

60
Tôi chưa bao giờ thực sự hiểu tại sao mọi người liên kết việc kết hợp tất cả logic vào một dòng duy nhất khiến nó trông bằng cách nào đó tốt hơn hoặc dễ đọc hơn. C ++ đoạn mã ở đầu rất là xa dễ đọc nhất đối với tôi trong số tất cả các khả năng của bạn. Và vì hiệu quả sẽ không thay đổi, tôi không thể hiểu tại sao bạn không muốn viết điều đó, trừ khi bạn được trả bằng số dòng mã mà bạn xóa.
Cody Grey

10
@CodyGray Đồng ý: nó chỉ là đường cú pháp. Và tiêu đề câu hỏi gây hiểu lầm, bởi vì nó rất khác tránh phân nhánh và ẩn nó dưới sự trừu tượng.
edmz

Câu trả lời:


99

IMHO sẽ dễ dàng hơn và dễ đọc hơn khi sử dụng vòng lặp for với if bên trong nó. Tuy nhiên, nếu điều này gây khó chịu cho bạn, bạn có thể sử dụng for_each_ifnhư sau:

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

Bản thử trực tiếp


10
Điều đó đặc biệt thông minh. Tôi cũng sẽ đồng ý rằng nó không thẳng thắn và có lẽ tôi sẽ chỉ sử dụng nếu các điều kiện khi lập trình C ++ được người khác sử dụng. Nhưng đó chính xác là những gì tôi cần cho mục đích sử dụng cá nhân của riêng tôi! :)
Darkenor vào

14
@Default Chuyển các cặp trình lặp thay vì vùng chứa là cả C ++ linh hoạt và dễ hiểu hơn.
Mark B

8
@Slava, nói chung phạm vi sẽ không làm giảm số lượng thuật toán. Ví dụ: bạn vẫn cần find_iffindliệu chúng có hoạt động trên phạm vi hoặc cặp biến lặp hay không. (Có một vài ngoại lệ, chẳng hạn như for_eachfor_each_n). Cách để tránh phải viết algos mới cho mỗi hắt hơi là sử dụng các hoạt động khác nhau với algos hiện có, ví dụ như thay vì for_each_ifnhúng điều kiện vào callable truyền cho for_each, ví dụ nhưfor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Jonathan Wakely

9
Tôi sẽ phải đồng ý với câu đầu tiên: Tiêu chuẩn cho-nếu giải pháp là nhiều hơn dễ đọc và dễ dàng hơn để làm việc với. Tôi nghĩ rằng cú pháp lambda và việc sử dụng một khuôn mẫu được xác định ở một nơi khác chỉ để xử lý một vòng lặp đơn giản sẽ gây khó chịu hoặc có thể gây nhầm lẫn cho các nhà phát triển khác. Bạn đang hy sinh địa phương và hiệu suất cho ... cái gì? Có thể viết một cái gì đó trong một dòng?
user1354557

45
Ho @Darkenor, nói chung là " đặc biệt thông minh" lập trình là để tránh vì nó làm phiền các crap ra khỏi những người khác trong đó có tương lai tự của bạn.
Ryan

48

Boost cung cấp các phạm vi có thể được sử dụng dựa trên phạm vi. Phạm vi có lợi thế mà họ không sao chép cấu trúc dữ liệu cơ bản, họ chỉ đơn thuần cung cấp một 'xem' (có nghĩa là, begin(), end()cho phạm vi và operator++(), operator==()cho iterator). Điều này có thể bạn quan tâm: 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
Tôi có thể đề nghị sử dụng ví dụ OPs thay thế không, tức là is_even=> condition, input=> myCollectionv.v.
Mặc định là

Đây là một câu trả lời khá xuất sắc và chắc chắn là điều tôi đang muốn làm. Tôi sẽ không chấp nhận trừ khi ai đó có thể đưa ra một cách tuân thủ tiêu chuẩn để thực hiện nó sử dụng thực thi lười biếng / trì hoãn. Đã ủng hộ.
Darkenor

5
@Darkenor: Nếu Boost là một vấn đề đối với bạn (ví dụ: bạn bị cấm sử dụng nó do chính sách của công ty và sự khôn ngoan của người quản lý), tôi có thể đưa ra một định nghĩa đơn giản filtered()cho bạn - điều đó nói rằng, tốt hơn là nên sử dụng một lib được hỗ trợ hơn một số mã đặc biệt.
lorro

Hoàn toàn đồng ý với bạn. Tôi chấp nhận nó vì cách tuân thủ tiêu chuẩn xuất hiện đầu tiên vì câu hỏi hướng đến chính C ++ chứ không phải thư viện tăng cường. Nhưng điều này thực sự xuất sắc. Ngoài ra - vâng, tôi đã buồn bã làm việc ở nhiều-một-nơi mà cấm Boost vì những lý do ngớ ngẩn ...
Darkenor

@LeeClagett :? .
lorro

44

Thay vì tạo một thuật toán mới, như câu trả lời được chấp nhận, bạn có thể sử dụng một thuật toán hiện có với một hàm áp dụng điều kiện:

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

Hoặc nếu bạn thực sự muốn một thuật toán mới, ít nhất hãy sử dụng lại for_eachở đó thay vì sao chép logic lặp:

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

Tốt hơn và rõ ràng hơn nhiều để sử dụng thư viện tiêu chuẩn.
nặc danh,

4
std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });là hoàn toàn đơn giản hơn for (Iter x = first; x != last; x++) if (p(x)) op(x);}?
user253751

2
@immibis sử dụng lại thư viện chuẩn có những lợi ích khác, chẳng hạn như kiểm tra tính hợp lệ của trình lặp hoặc (trong C ++ 17) dễ dàng hơn để song song hóa, chỉ đơn giản bằng cách thêm một đối số nữa: std::for_each(std::execution::par, first, last, ...);Thêm những thứ đó vào vòng lặp viết tay dễ dàng như thế nào?
Jonathan Wakely

1
#pragma OMP song song cho
Đánh K Cowan

2
@mark xin lỗi, một số lỗi ngẫu nhiên trong mã nguồn hoặc chuỗi xây dựng của bạn đã làm cho tiện ích mở rộng trình biên dịch song song không chuẩn dễ hỏng gây khó chịu đó tạo ra hiệu suất tăng bằng không mà không có chẩn đoán.
Yakk - Adam Nevraumont

21

Ý tưởng tránh

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

cấu trúc như một phản vật chất quá rộng.

Hoàn toàn tốt khi xử lý nhiều mục khớp với một biểu thức nhất định từ bên trong một vòng lặp và mã không thể rõ ràng hơn thế. Nếu quá trình xử lý phát triển quá lớn để phù hợp với màn hình, đó là lý do chính đáng để sử dụng chương trình con, nhưng điều kiện vẫn tốt nhất được đặt bên trong vòng lặp, tức là

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

rất thích

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

Nó trở thành phản vật chất khi chỉ có một phần tử khớp với nhau, bởi vì khi đó sẽ rõ ràng hơn khi tìm kiếm phần tử và thực hiện xử lý bên ngoài vòng lặp.

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

là một ví dụ cực đoan và hiển nhiên của điều này. Tinh tế hơn, và do đó phổ biến hơn, là mẫu nhà máy như

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

Điều này khó đọc, bởi vì không rõ ràng rằng mã nội dung sẽ được thực thi một lần duy nhất. Trong trường hợp này, tốt hơn nên tách riêng phần tra cứu:

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

Vẫn có một ifbên trong a for, nhưng từ ngữ cảnh, nó trở nên rõ ràng nó làm gì, không cần phải thay đổi mã này trừ khi tra cứu thay đổi (ví dụ: thành a map) và ngay lập tức rõ ràng rằng create_object()nó chỉ được gọi một lần, bởi vì nó không bên trong một vòng lặp.


Tôi thích điều này, như một cái nhìn tổng quan cân bằng và chu đáo, ngay cả khi nó từ chối trả lời câu hỏi đặt ra. Tôi thấy rằng for( range ){ if( condition ){ action } }-style giúp bạn dễ dàng đọc từng thứ một và chỉ sử dụng kiến ​​thức về các cấu trúc ngôn ngữ cơ bản.
PJTraill

@PJTraill, cách diễn đạt câu hỏi khiến tôi nhớ đến lời nói của Raymond Chen chống lại phản vật chất for-if , thứ đã được sùng bái và bằng cách nào đó đã trở thành một thứ tuyệt đối. Tôi hoàn toàn đồng ý rằng đó for(...) if(...) { ... }thường là lựa chọn tốt nhất (đó là lý do tại sao tôi đủ điều kiện đề xuất chia hành động thành một chương trình con).
Simon Richter

1
Cảm ơn vì liên kết đã làm rõ mọi thứ cho tôi: tên “ for-if ” gây hiểu lầm và phải là “ for-all-if-one ” hoặc “ lookup-tránh ”. Nó làm tôi nhớ đến cách đảo ngược Trừu tượng được Wikipedia mô tả vào năm 2005 như khi một người “ tạo ra các cấu trúc đơn giản bên trên các cấu trúc phức tạp ” - cho đến khi tôi viết lại nó! Trên thực tế, tôi thậm chí sẽ không vội sửa biểu mẫu tra cứu-quy trình-thoát for(…)if(…)…nếu đó là lần tra cứu địa điểm duy nhất xảy ra.
PJTraill,

17

Đây là một nhanh tương đối tối thiểu filter chức năng .

Nó có một vị ngữ. Nó trả về một đối tượng hàm có thể lặp lại.

Nó trả về một có thể lặp lại có thể được sử dụng trong một for(:)vòng lặp.

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

Tôi đã đi tắt đón đầu. Một thư viện thực sự phải tạo ra các trình lặp thực sự, chứ không phải là các trình lặp for(:)giả mạo đủ tiêu chuẩn như tôi đã làm.

Tại thời điểm sử dụng, nó trông như thế này:

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

cái đó khá đẹp và bản in

1
3
5

Ví dụ trực tiếp .

Có một bổ sung được đề xuất cho C ++ được gọi là Rangesv3 thực hiện loại điều này và hơn thế nữa. boostcũng có sẵn phạm vi bộ lọc / vòng lặp. boost cũng có những người trợ giúp làm cho việc viết ở trên ngắn hơn nhiều.


15

Một phong cách được sử dụng đủ để đề cập, nhưng vẫn chưa được đề cập, là:

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

  DoStuff();
}

Ưu điểm:

  • Không thay đổi mức thụt lề DoStuff();khi độ phức tạp của điều kiện tăng lên. Về mặt logic, DoStuff();phải ở cấp cao nhất của forvòng lặp, và đúng như vậy.
  • Ngay lập tức làm cho nó rõ ràng rằng lặp loop qua SOMETHINGs của bộ sưu tập, mà không đòi hỏi người đọc để xác minh rằng không có gì sau khi kết thúc là }của ifkhối.
  • Không yêu cầu bất kỳ thư viện hoặc macro hoặc chức năng trợ giúp nào.

Nhược điểm:

  • continue, giống như các câu lệnh điều khiển luồng khác, bị lạm dụng theo những cách dẫn đến mã khó theo dõi đến mức một số người phản đối bất kỳ cách sử dụng nào của chúng: có một kiểu mã hợp lệ mà một số người tuân theo thì tránh continue, kiểu đó tránh breakkhác trong a switch, tránh returnkhác với ở cuối một hàm.

3
Tôi lập luận rằng trong một forvòng lặp kéo dài đến nhiều dòng, hai dòng "nếu không, hãy tiếp tục" rõ ràng, logic và dễ đọc hơn nhiều. Ngay lập tức nói, "bỏ qua điều này nếu" sau khi forcâu lệnh đọc tốt và, như bạn đã nói, không thụt lề các khía cạnh chức năng còn lại của vòng lặp. continueTuy nhiên, nếu càng xuống thấp, một số rõ ràng sẽ bị hy sinh (tức là nếu một số thao tác sẽ luôn được thực hiện trước ifcâu lệnh).
ẩn danh,

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

Trông khá giống với C ++ - một forcách hiểu cụ thể đối với tôi. Đối với bạn?


Tôi không nghĩ rằng từ khóa tự động đã có trước c ++ 11 nên tôi sẽ không nói nó là c ++ cổ điển. Nếu tôi có thể đặt một câu hỏi ở đây trong nhận xét, liệu "auto const" có nói với trình biên dịch rằng nó có thể sắp xếp lại tất cả các phần tử như nó muốn không? Có thể trình biên dịch sẽ dễ dàng hơn trong việc lập kế hoạch tránh phân nhánh nếu trường hợp đó xảy ra.
mathreadler

1
@mathreadler Mọi người càng sớm ngừng lo lắng về "c ++ cổ điển" thì càng tốt. C ++ 11 là một sự kiện cách mạng vĩ mô đối với ngôn ngữ và đã được 5 năm tuổi: đó phải là mức tối thiểu mà chúng tôi cố gắng đạt được. Dù sao, OP đã gắn thẻ đó và C ++ 14 (thậm chí còn tốt hơn!). Không, auto constkhông liên quan gì đến thứ tự lặp lại. Nếu bạn tìm kiếm dựa trên phạm vi for, bạn sẽ thấy rằng về cơ bản nó thực hiện một vòng lặp tiêu chuẩn từ begin()đến end()với hội nghị truyền thống ngầm. Không có cách nào nó có thể phá vỡ các đảm bảo sắp xếp (nếu có) của vùng chứa đang được lặp lại; nó sẽ bị cười ra khỏi mặt Trái đất
underscore_d

1
@mathreadler, thực ra là như vậy, nó có một ý nghĩa khác. Những gì không có mặt là phạm vi-cho ... và bất kỳ tính năng C ++ 11 khác biệt nào khác. Ý tôi muốn nói ở đây là phạm vi-fors, std::futures, std::functions, thậm chí cả những bao đóng ẩn danh đó là C ++ ish rất tốt trong cú pháp; mọi ngôn ngữ đều có cách nói riêng và khi kết hợp các tính năng mới, nó sẽ cố gắng làm cho chúng bắt chước cú pháp cũ nổi tiếng.
bipll

@underscore_d, một trình biên dịch được phép thực hiện bất kỳ phép biến đổi nào miễn là tuân theo quy tắc as-if, phải không?
bipll

1
Hmmm, và điều đó có thể có nghĩa là gì?
bipll

7

Nếu DoStuff () sẽ phụ thuộc vào tôi bằng cách nào đó trong tương lai thì tôi sẽ đề xuất biến thể che mặt nạ bit không nhánh được đảm bảo này.

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

Trong đó popcount là bất kỳ hàm nào thực hiện đếm dân số (đếm số bit = 1). Sẽ có một số quyền tự do để đưa ra những ràng buộc nâng cao hơn với tôi và những người hàng xóm của họ. Nếu điều đó không cần thiết, chúng tôi có thể tháo vòng trong và làm lại vòng ngoài

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

Theo sau là một

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

6

Ngoài ra, nếu bạn không quan tâm đến việc sắp xếp lại bộ sưu tập, phân vùng std :: rất rẻ.

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

Nhưng std::partitionsắp xếp lại vùng chứa.
celtschk

5

Tôi kinh ngạc về sự phức tạp của các giải pháp trên. Tôi sẽ đề xuất một cách đơn giản #define foreach(a,b,c,d) for(a; b; c)if(d)nhưng nó có một vài thiếu sót rõ ràng, ví dụ, bạn phải nhớ sử dụng dấu phẩy thay vì dấu chấm phẩy trong vòng lặp của bạn và bạn không thể sử dụng toán tử dấu phẩy trong ahoặc c.

#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
Độ phức tạp của một số câu trả lời chỉ cao vì trước tiên chúng hiển thị một phương pháp chung có thể tái sử dụng (bạn chỉ thực hiện một lần) và sau đó sử dụng nó. Không hiệu quả nếu bạn có một vòng lặp với điều kiện if trong toàn bộ ứng dụng của bạn nhưng rất hiệu quả nếu nó xảy ra hàng nghìn lần.
gnasher729

1
Giống như hầu hết các đề xuất, điều này làm cho việc xác định phạm vi và điều kiện lựa chọn khó hơn, không dễ hơn. Và việc sử dụng macro làm tăng sự không chắc chắn về thời điểm (và tần suất) các biểu thức được đánh giá, ngay cả khi không có bất ngờ nào ở đây.
PJTraill

2

Một giải pháp khác trong trường hợp i: s là quan trọng. Cái này xây dựng một danh sách điền vào các chỉ mục để gọi doStuff () cho. Một lần nữa, điểm chính là tránh phân nhánh và đổi nó lấy chi phí số học có thể chuyển đổi.

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.

Dòng "ma thuật" là dòng tải bộ đệm tính toán số học làm ướt để giữ giá trị và giữ nguyên vị trí hoặc để đếm lên vị trí và thêm giá trị. Vì vậy, chúng tôi đánh đổi một nhánh tiềm năng cho một số lôgic học và số học và có thể một số lần truy cập bộ nhớ cache. Một kịch bản điển hình khi điều này sẽ hữu ích là nếu doStuff () thực hiện một số lượng nhỏ các phép tính có thể chuyển hướng và bất kỳ nhánh nào ở giữa các cuộc gọi có thể làm gián đoạn các đường ống đó.

Sau đó, chỉ cần lặp qua bộ đệm và chạy doStuff () cho đến khi chúng ta đạt đến cnt. Lần này chúng ta sẽ lưu trữ i hiện tại trong bộ đệm để chúng ta có thể sử dụng nó trong lệnh gọi doStuff () nếu chúng ta cần.


1

Người ta có thể mô tả mẫu mã của bạn là áp dụng một số chức năng cho một tập hợp con của một dải ô, hay nói cách khác: áp dụng nó cho kết quả của việc áp dụng một bộ lọc cho toàn bộ dải ô.

Điều này có thể đạt được theo cách đơn giản nhất với thư viện range-v3 của Eric Neibler ; mặc dù nó hơi chướng mắt, vì bạn muốn làm việc với các chỉ số:

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

Nhưng nếu bạn sẵn sàng bỏ qua các chỉ số, bạn sẽ nhận được:

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 đẹp hơn.

PS - Thư viện phạm vi chủ yếu đi vào tiêu chuẩn C ++ trong C ++ 20.


0

Tôi sẽ chỉ đề cập đến Mike Acton, anh ấy chắc chắn sẽ nói:

Nếu bạn phải làm điều đó, bạn có vấn đề với dữ liệu của mình. Sắp xếp dữ liệu của bạn!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.