วิธีที่มีประสิทธิภาพมากที่สุดในการรับดัชนีตัววนซ้ำของ std :: vector คืออะไร


439

ฉันกำลังวนซ้ำเวกเตอร์และต้องการดัชนีที่ตัววนซ้ำกำลังชี้ไปที่ AFAIK สามารถทำได้สองวิธี:

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

อะไรคือข้อดีข้อเสียของวิธีการเหล่านี้?

คำตอบ:


558

ฉันอยากit - vec.begin()ได้อย่างแม่นยำด้วยเหตุผลตรงข้ามที่ Naveen ให้ไว้: ดังนั้นมันจะไม่รวบรวมถ้าคุณเปลี่ยนเวกเตอร์เป็นรายการ หากคุณทำสิ่งนี้ในระหว่างการทำซ้ำทุกครั้งคุณสามารถเปลี่ยนอัลกอริทึม O (n) เป็นอัลกอริทึม O (n ^ 2) ได้อย่างง่ายดาย

ตัวเลือกอื่นถ้าคุณไม่ข้ามไปมาในคอนเทนเนอร์ระหว่างการวนซ้ำจะเป็นการเก็บดัชนีเป็นตัวนับลูปที่สอง

หมายเหตุ: itเป็นชื่อสามัญสำหรับ iterator std::container_type::iterator it;คอนเทนเนอร์


3
ตกลง ฉันจะบอกว่าเครื่องหมายลบที่ดีที่สุด แต่มันจะดีกว่าที่จะรักษาวนลูปที่สองกว่าการใช้ std :: distance เพราะฟังก์ชั่นนี้อาจช้า
Steven Sudit

28
ห่าคือitอะไร
Steinfeld

32
@ Steinfeld เป็นตัววนซ้ำ std::container_type::iterator it;
Matt Munson

2
การเพิ่มตัวนับลูปที่สองเป็นโซลูชันที่ชัดเจนที่ฉันอายฉันไม่ได้คิด
Mordred

3
@Swapnil เพราะstd::listไม่ได้มีการเข้าถึงโดยตรงไปยังองค์ประกอบโดยตำแหน่งของพวกเขาดังนั้นหากคุณไม่สามารถทำคุณไม่ควรจะสามารถที่จะทำlist[5] list.begin() + 5
JoséTomás Tocino

135

ฉันต้องการstd::distance(vec.begin(), it)จะให้ฉันเปลี่ยนคอนเทนเนอร์ได้โดยไม่ต้องเปลี่ยนรหัสใด ๆ ตัวอย่างเช่นหากคุณตัดสินใจที่จะใช้std::listแทนที่จะstd::vectorไม่มีตัววนซ้ำการเข้าถึงแบบสุ่มรหัสของคุณจะยังคงรวบรวม เนื่องจาก std :: distance เลือกวิธีที่ดีที่สุดขึ้นอยู่กับลักษณะของตัววนซ้ำคุณจะไม่ทำให้ประสิทธิภาพลดลงเช่นกัน


50
เมื่อคุณใช้คอนเทนเนอร์ที่ไม่มีตัววนซ้ำการเข้าถึงแบบสุ่มวิธีที่ดีที่สุดคือไม่คำนวณระยะทางดังกล่าวเพราะมันไม่มีประสิทธิภาพ
Eli Bendersky

6
@Eli: ฉันเห็นด้วยกับมัน แต่ในกรณีพิเศษมากหากจำเป็นจริงๆรหัสก็ยังคงใช้ได้
Naveen

9
ฉันคิดว่ารหัสควรมีการเปลี่ยนแปลงต่อไปหากการเปลี่ยนแปลงภาชนะ - มีตัวแปร std :: list ชื่อvecข่าวร้าย หากรหัสถูกเขียนใหม่ให้เป็นแบบทั่วไปโดยใช้ประเภทคอนเทนเนอร์เป็นพารามิเตอร์เทมเพลตนั่นคือเมื่อเราสามารถ (และควร) พูดคุยเกี่ยวกับการจัดการตัววนซ้ำแบบไม่เข้าถึง ;-)
Steve Jessop

1
และความเชี่ยวชาญสำหรับภาชนะบางอย่าง
ScaryAardvark

19
@SteveJessop: การมีเวกเตอร์ชื่อว่าvecเป็นข่าวร้ายเช่นกัน
แม่น้ำ Tam Tam

74

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

it - vec.begin()ใช้เวลาคงที่ แต่operator -มีการกำหนดไว้เฉพาะในตัววนซ้ำการเข้าถึงแบบสุ่มดังนั้นโค้ดจะไม่คอมไพล์เลยด้วยตัววนซ้ำรายการตัวอย่างเช่น

std::distance(vec.begin(), it) ใช้งานได้กับตัววนซ้ำทุกประเภท แต่จะเป็นการดำเนินการแบบคงที่เวลาหากใช้กับตัววนซ้ำการเข้าถึงแบบสุ่ม

ไม่มีอย่างใดอย่างหนึ่งคือ "ดีกว่า" ใช้สิ่งที่คุณต้องการ


1
ในอดีตที่ผ่านมาฉันทำผิดพลาดไปแล้ว ใช้ std :: distance บน std :: map iterators สองตัวและคาดหวังว่ามันจะเป็น O (N)
ScaryAardvark

6
@ScaryAardvark: คุณไม่ได้คาดหวังว่าจะเป็น O (1) ใช่ไหม
jalf

12

ฉันชอบอันนี้: it - vec.begin()เพราะสำหรับฉันมันชัดเจนว่า "ระยะทางจากจุดเริ่มต้น" ด้วยตัววนซ้ำเราใช้ความคิดในแง่ของเลขคณิตดังนั้น-เครื่องหมายจึงเป็นตัวบ่งชี้ที่ชัดเจนที่สุดที่นี่


19
เป็นที่ชัดเจนมากขึ้นในการใช้งานการลบเพื่อหาระยะทางกว่าที่จะใช้งานค่อนข้างอักษรคำว่าdistance?
Travis Gockel

4
@ Travis สำหรับฉันมันเป็น มันเป็นเรื่องของรสนิยมและขนบธรรมเนียม เราพูดit++และไม่ชอบstd::increment(it)อะไรใช่ไหม นั่นจะไม่นับรวมว่าชัดเจนน้อยลงหรือไม่
Eli Bendersky

3
ตัว++ดำเนินการถูกกำหนดเป็นส่วนหนึ่งของลำดับ STL เป็นวิธีที่เราเพิ่มตัววนซ้ำ std::distanceคำนวณจำนวนขององค์ประกอบระหว่างองค์ประกอบแรกและสุดท้าย ความจริงที่ว่า-ผู้ปฏิบัติงานใช้งานเป็นเพียงเรื่องบังเอิญ
Travis Gockel

3
@MSalters: และถึงกระนั้นเราก็ใช้ ++ :-)
Eli Bendersky

10

หากคุณถูก จำกัด / ฮาร์ดโค้ดอัลกอริทึมของคุณให้ใช้ a std::vector::iteratorและstd::vector::iteratorเพียงอย่างเดียวก็ไม่สำคัญว่าวิธีการใดที่คุณจะใช้ อัลกอริทึมของคุณได้รับการ concretized เกินกว่าจุดที่เลือกอย่างใดอย่างหนึ่งสามารถสร้างความแตกต่างใด ๆ พวกเขาทั้งสองทำสิ่งเดียวกัน มันเป็นเพียงเรื่องของการตั้งค่าส่วนตัว ฉันจะใช้การลบอย่างชัดเจนเป็นการส่วนตัว

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

  • หากคุณใช้การลบอย่างชัดเจนอัลกอริทึมของคุณจะถูก จำกัด ให้อยู่ในระดับที่ค่อนข้างแคบของตัววนซ้ำ: ตัววนซ้ำการเข้าถึงแบบสุ่ม (นี่คือสิ่งที่คุณได้รับตอนนี้std::vector)

  • หากคุณใช้distanceอัลกอริทึมของคุณจะสนับสนุนคลาสตัววนซ้ำที่กว้างขึ้น: อินพุตตัววนซ้ำ

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


4

ตามhttp://www.cplusplus.com/reference/std/iterator/distance/เนื่องจากvec.begin()เป็นตัววนซ้ำการเข้าถึงแบบสุ่มวิธีระยะทางจะใช้-โอเปอเรเตอร์

ดังนั้นคำตอบคือจากมุมมองของประสิทธิภาพมันเหมือนกัน แต่บางทีการใช้อาจdistance()จะง่ายกว่าที่จะเข้าใจถ้าใครจะต้องอ่านและเข้าใจโค้ดของคุณ


3

ฉันใช้-ตัวแปรstd::vectorเพียงอย่างเดียว - มันค่อนข้างชัดเจนว่ามีความหมายอะไรและความเรียบง่ายของการดำเนินการ (ซึ่งไม่มากกว่าการลบตัวชี้) แสดงโดยไวยากรณ์ ( distanceในอีกด้านหนึ่งดูเหมือนว่าพีทาโกรัสบน อ่านครั้งแรกใช่ไหม?) เป็นจุด UncleBen ออก-ยังทำหน้าที่เป็นยืนยันคงที่ในกรณีที่มีการเปลี่ยนแปลงไปvector accidentiallylist

นอกจากนี้ฉันคิดว่ามันเป็นเรื่องธรรมดามาก - ไม่มีตัวเลขที่จะพิสูจน์ได้ อาร์กิวเมนต์หลัก: it - vec.begin()สั้นกว่าในซอร์สโค้ด - งานพิมพ์น้อยลงใช้พื้นที่น้อยลง ในขณะที่มันเป็นที่ชัดเจนว่าคำตอบที่เหมาะสมสำหรับคำถามของคุณเดือดลงไปเป็นเรื่องของรสชาตินี้จะยังเป็นอาร์กิวเมนต์ที่ถูกต้อง


0

นี่คือตัวอย่างเพื่อค้นหาเหตุการณ์ "ทั้งหมด" 10 รายการพร้อมกับดัชนี คิดว่านี่จะช่วยได้บ้าง

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