Cách hiệu quả nhất để có được chỉ số của một trình vòng lặp của một std :: vector là gì?


438

Tôi đang lặp qua một vectơ và cần chỉ mục mà trình lặp hiện đang trỏ tới. AFAIK điều này có thể được thực hiện theo hai cách:

  • it - vec.begin()
  • std::distance(vec.begin(), it)

Những ưu và nhược điểm của các phương pháp này là gì?

Câu trả lời:


557

Tôi thích it - vec.begin()chính xác hơn cho lý do ngược lại được đưa ra bởi Naveen: vì vậy nó sẽ không biên dịch nếu bạn thay đổi vectơ thành một danh sách. Nếu bạn làm điều này trong mỗi lần lặp, bạn có thể dễ dàng biến thuật toán O (n) thành thuật toán O (n ^ 2).

Một tùy chọn khác, nếu bạn không nhảy xung quanh trong container trong vòng lặp, sẽ là giữ chỉ mục làm bộ đếm vòng lặp thứ hai.

Lưu ý: itlà tên chung cho trình lặp container , std::container_type::iterator it;.


3
Đã đồng ý. Tôi muốn nói rằng dấu trừ là tốt nhất, nhưng sẽ tốt hơn nếu giữ bộ đếm vòng lặp thứ hai hơn là sử dụng std :: distance, chính xác là vì chức năng này có thể chậm.
Steven Sudit

28
cái quái itgì thế
Steinfeld

32
@Steinfeld nó là một trình vòng lặp. std::container_type::iterator it;
Matt Munson

2
Thêm một bộ đếm vòng lặp thứ hai là một giải pháp rõ ràng đến nỗi tôi cảm thấy xấu hổ, tôi đã không nghĩ về nó.
Sắp xếp

3
@Swapnil vì std::listkhông cung cấp quyền truy cập trực tiếp vào các yếu tố theo vị trí của chúng, vì vậy nếu bạn không thể làm list[5], bạn không nên làm list.begin() + 5.
Jose Tomás Tocino

135

Tôi thích std::distance(vec.begin(), it)vì nó sẽ cho phép tôi thay đổi container mà không có bất kỳ thay đổi mã nào. Ví dụ: nếu bạn quyết định sử dụng std::listthay vì std::vectorkhông cung cấp trình lặp truy cập ngẫu nhiên, mã của bạn sẽ vẫn biên dịch. Vì std :: distance chọn phương thức tối ưu tùy thuộc vào các đặc điểm của trình vòng lặp, bạn cũng sẽ không bị suy giảm hiệu năng.


50
Khi bạn đang sử dụng bộ chứa mà không có bộ lặp truy cập ngẫu nhiên, tốt nhất không nên tính khoảng cách như vậy vì nó không hiệu quả
Eli Bendersky

6
@Eli: Tôi đồng ý với điều đó, nhưng trong một trường hợp rất đặc biệt nếu nó thực sự cần thiết, thì mã đó vẫn hoạt động.
Naveen

9
Tôi nghĩ rằng dù sao thì mã cũng nên được thay đổi nếu container thay đổi - có biến std :: list có tên veclà tin xấu. Nếu mã được viết lại thành chung chung, lấy loại vùng chứa làm tham số mẫu, đó là khi chúng ta có thể (và nên) nói về việc xử lý các trình vòng lặp truy cập không ngẫu nhiên ;-)
Steve Jessop

1
Và chuyên môn hóa cho các container nhất định.
ScaryAardvark

19
@SteveJessop: Có một vector tên veclà tin xấu, quá.
Sông Tam

74

Như UncleBens và Naveen đã chỉ ra, có những lý do tốt cho cả hai. Cái nào là "tốt hơn" phụ thuộc vào hành vi bạn muốn: Bạn có muốn đảm bảo hành vi không đổi thời gian, hoặc bạn muốn nó quay trở lại thời gian tuyến tính khi cần thiết?

it - vec.begin()mất thời gian liên tục, nhưng operator -chỉ được xác định trên các trình vòng lặp truy cập ngẫu nhiên, do đó, mã sẽ không biên dịch được tất cả với các trình vòng lặp danh sách, ví dụ.

std::distance(vec.begin(), it) hoạt động cho tất cả các loại trình vòng lặp, nhưng sẽ chỉ là một hoạt động thời gian không đổi nếu được sử dụng trên các trình vòng lặp truy cập ngẫu nhiên.

Không ai là "tốt hơn". Sử dụng một trong đó làm những gì bạn cần.


1
Tôi đã phạm lỗi này trong quá khứ. Sử dụng std :: distance trên hai std :: map iterators và hy vọng nó là O (N).
ScaryAardvark

6
@ScaryAardvark: Không có nghĩa là bạn mong đợi nó là O (1)?
jalf

12

Tôi thích cái này: it - vec.begin()vì với tôi nó nói rõ ràng "khoảng cách từ đầu". Với các trình vòng lặp, chúng ta thường nghĩ về mặt số học, vì vậy -dấu hiệu là chỉ số rõ ràng nhất ở đây.


19
Rõ ràng hơn là sử dụng phép trừ để tìm khoảng cách hơn là sử dụng, theo đúng nghĩa đen của từ này distance?
Travis Gockel

4
@Travis, với tôi là vậy. Đó là một vấn đề của hương vị và tùy chỉnh. Chúng ta nói it++và không phải như thế std::increment(it), phải không? Điều đó cũng không được tính là ít rõ ràng hơn?
Eli Bendersky

3
Các ++nhà điều hành được định nghĩa như là một phần của chuỗi STL như thế nào chúng ta tăng các iterator. std::distancetính toán số phần tử giữa phần tử đầu tiên và phần tử cuối cùng. Thực tế là các -nhà điều hành làm việc chỉ là một sự trùng hợp ngẫu nhiên.
Travis Gockel

3
@MSalters: và chưa, chúng tôi sử dụng ++ :-)
Eli Bendersky

10

Nếu bạn đã bị hạn chế / mã hóa thuật toán của mình để sử dụng một std::vector::iteratorstd::vector::iteratorchỉ, thì thực sự bạn sẽ không sử dụng phương pháp nào. Thuật toán của bạn đã được cụ thể hóa vượt quá điểm mà việc chọn một trong những thuật toán khác có thể tạo ra bất kỳ sự khác biệt nào. Cả hai đều làm chính xác cùng một điều. Nó chỉ là một vấn đề sở thích cá nhân. Cá nhân tôi sẽ sử dụng phép trừ rõ ràng.

Mặt khác, nếu bạn muốn duy trì mức độ tổng quát cao hơn trong thuật toán của mình, cụ thể là, cho phép khả năng một ngày nào đó trong tương lai nó có thể được áp dụng cho một số kiểu lặp khác, thì phương pháp tốt nhất phụ thuộc vào ý định của bạn . Nó phụ thuộc vào mức độ hạn chế mà bạn muốn liên quan đến loại trình vòng lặp có thể được sử dụng ở đây.

  • Nếu bạn sử dụng phép trừ rõ ràng, thuật toán của bạn sẽ bị giới hạn trong một lớp lặp khá hẹp: các trình lặp truy cập ngẫu nhiên. (Đây là những gì bạn nhận được bây giờ từ std::vector)

  • Nếu bạn sử dụng distance, thuật toán của bạn sẽ hỗ trợ một lớp các trình vòng lặp rộng hơn nhiều: các trình vòng lặp đầu vào.

Tất nhiên, tính toán distancecho các trình vòng lặp truy cập không ngẫu nhiên trong trường hợp nói chung là một hoạt động không hiệu quả (trong khi, một lần nữa, đối với các truy cập ngẫu nhiên, nó hiệu quả như phép trừ). Tùy thuộc vào bạn để quyết định xem thuật toán của bạn có hợp lý với các trình vòng lặp truy cập không ngẫu nhiên hay không, hiệu quả. Do đó, sự mất hiệu quả dẫn đến tàn phá đến mức làm cho thuật toán của bạn hoàn toàn vô dụng, sau đó bạn nên sử dụng phép trừ, do đó cấm sử dụng không hiệu quả và buộc người dùng phải tìm giải pháp thay thế cho các loại trình vòng lặp khác. Nếu hiệu quả với các trình vòng lặp truy cập không ngẫu nhiên vẫn nằm trong phạm vi có thể sử dụng, thì bạn nên sử dụng distancevà ghi lại thực tế rằng thuật toán hoạt động tốt hơn với các trình vòng lặp truy cập ngẫu nhiên.


4

Theo http://www.cplusplus.com/reference/std/iterator/distance/ , vì vec.begin()là một trình vòng lặp truy cập ngẫu nhiên , phương thức khoảng cách sử dụng -toán tử.

Vì vậy, câu trả lời là, từ quan điểm hiệu suất, nó là như nhau, nhưng có thể sử dụng distance()sẽ dễ hiểu hơn nếu có ai phải đọc và hiểu mã của bạn.


3

Tôi chỉ sử dụng -biến thể std::vector- nó khá rõ nghĩa là gì và tính đơn giản của thao tác (không phải là phép trừ con trỏ) được thể hiện bằng cú pháp ( distance, mặt khác, nghe giống như tiếng pythagoras trên đọc lần đầu phải không?). Như chúBen chỉ ra, -cũng hoạt động như một xác nhận tĩnh trong trường hợp vectorđược thay đổi ngẫu nhiên thành list.

Ngoài ra tôi nghĩ rằng nó phổ biến hơn nhiều - mặc dù không có con số để chứng minh điều đó. Đối số chính: it - vec.begin()ngắn hơn trong mã nguồn - công việc gõ ít hơn, tiêu tốn ít dung lượng hơn. Vì rõ ràng rằng câu trả lời đúng cho câu hỏi của bạn là vấn đề của hương vị, đây cũng có thể là một lý lẽ hợp lệ.


0

Dưới đây là một ví dụ để tìm "tất cả" số lần xuất hiện của 10 cùng với chỉ mục. Nghĩ rằng điều này sẽ có ích.

void _find_all_test()
{
    vector<int> ints;
    int val;
    while(cin >> val) ints.push_back(val);

    vector<int>::iterator it;
    it = ints.begin();
    int count = ints.size();
    do
    {
        it = find(it,ints.end(), 10);//assuming 10 as search element
        cout << *it << " found at index " << count -(ints.end() - it) << endl;
    }while(++it != ints.end()); 
}
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.