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


274

ฉันต้องใช้เวกเตอร์ C ++ ที่อาจมีองค์ประกอบจำนวนมากลบรายการที่ซ้ำกันและเรียงลำดับ

ขณะนี้ฉันมีรหัสด้านล่าง แต่มันใช้งานไม่ได้

vec.erase(
      std::unique(vec.begin(), vec.end()),
      vec.end());
std::sort(vec.begin(), vec.end());

ฉันจะทำอย่างถูกต้องได้อย่างไร

นอกจากนี้มันจะเร็วกว่าที่จะลบรายการที่ซ้ำก่อน (คล้ายกับรหัสด้านบน) หรือดำเนินการเรียงลำดับก่อนหรือไม่ หากฉันทำการเรียงลำดับก่อนจะรับประกันว่าจะยังคงเรียงลำดับหลังจากstd::uniqueถูกเรียกใช้งานหรือไม่?

หรือมีวิธีอื่น (อาจมีประสิทธิภาพมากกว่า) ในการทำสิ่งนี้ทั้งหมดหรือไม่


3
ฉันคิดว่าคุณไม่มีตัวเลือกในการตรวจสอบก่อนที่จะแทรกเพื่อหลีกเลี่ยงการหลอกในครั้งแรก?
Joe

แก้ไข. นั่นจะเหมาะ
Kyle Ryan

29
ฉันขอแนะนำให้แก้ไขรหัสข้างต้นหรือระบุว่าผิด std :: unique สันนิษฐานว่าเป็นช่วงที่เรียงลำดับแล้ว
Matthieu M.

คำตอบ:


584

ฉันเห็นด้วยกับR. PateและTodd Gardner ; std::setอาจเป็นความคิดที่ดีที่นี่ แม้ว่าคุณจะติดอยู่กับการใช้เวกเตอร์ แต่ถ้าคุณมีรายการที่ซ้ำกันมากพอคุณอาจจะดีกว่าถ้าสร้างชุดเพื่อทำงานที่สกปรก

ลองเปรียบเทียบสามวิธี:

เพียงใช้เวกเตอร์ให้เรียง + เฉพาะ

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

แปลงเป็นตั้งค่า (ด้วยตนเอง)

set<int> s;
unsigned size = vec.size();
for( unsigned i = 0; i < size; ++i ) s.insert( vec[i] );
vec.assign( s.begin(), s.end() );

แปลงเป็นชุด (ใช้คอนสตรัคเตอร์)

set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );

นี่คือวิธีการเหล่านี้ทำงานเป็นจำนวนซ้ำการเปลี่ยนแปลง:

การเปรียบเทียบเวกเตอร์และเซ็ตการเข้าใกล้

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

และด้วยเหตุผลบางอย่างการทำชุดแปลงด้วยตนเองดูเหมือนจะเร็วกว่าการใช้ตัวสร้างชุด - อย่างน้อยก็กับข้อมูลสุ่มของเล่นที่ฉันใช้


61
ฉันตกใจว่าวิธีการสร้างนั้นแย่กว่าคู่มืออย่างสม่ำเสมอ คุณต้องการที่นอกเหนือจากค่าใช้จ่ายคงที่เล็ก ๆ น้อย ๆ มันจะทำสิ่งที่ทำด้วยมือ มีใครอธิบายเรื่องนี้ได้บ้าง
Ari

17
เจ๋งขอบคุณสำหรับกราฟ คุณสามารถให้ความรู้สึกของหน่วยที่เป็นจำนวนซ้ำได้หรือไม่ (เช่นประมาณว่า "ใหญ่พอ" ใหญ่แค่ไหน)?
ไคล์ไรอัน

5
@ ไคล์: มันค่อนข้างใหญ่ ฉันใช้ชุดข้อมูลของจำนวนเต็ม 1,000,000 จำนวนสุ่มที่สุ่มระหว่าง 1 ถึง 1,000, 100 และ 10 สำหรับกราฟนี้
Nate Kohl

5
ฉันคิดว่าผลลัพธ์ของคุณผิด ในการทดสอบของฉันองค์ประกอบที่ซ้ำซ้อนมากขึ้นเวกเตอร์ที่เร็วขึ้น (การเปรียบเทียบ) คือจริง ๆ แล้วทำการสเกลวิธีอื่น คุณได้คอมไพล์ด้วยการออปติไมซ์ออนและเช็คเอาต์รันไทม์หรือไม่? ที่เวกเตอร์ด้านของฉันนั้นเร็วกว่ามากถึง 100 เท่าขึ้นอยู่กับจำนวนของการทำซ้ำ VS2013, cl / Ox -D_SECURE_SCL = 0
davidnr

39
คำอธิบายของแกน x ดูเหมือนว่าจะหายไป
BartoszKP

72

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

โปรดทราบว่าunordered_setวิธีการนี้ใช้งานได้เฉพาะเมื่อคุณมีฟังก์ชันแฮชที่ดีสำหรับประเภทที่คุณต้องการและไม่เรียงลำดับ สำหรับ ints มันง่ายมาก! (ไลบรารี่มาตรฐานให้แฮชเริ่มต้นซึ่งเป็นเพียงฟังก์ชั่นเอกลักษณ์) นอกจากนี้อย่าลืมเรียงลำดับตอนท้ายเนื่องจาก unordered_set นั้นดีไม่มีการจัดเรียง :)

ฉันทำการขุดภายในsetและunordered_setนำไปใช้และพบว่าตัวสร้างสร้างโหนดใหม่สำหรับทุกองค์ประกอบก่อนตรวจสอบค่าเพื่อดูว่าควรแทรกจริงหรือไม่ (ในการใช้งาน Visual Studio อย่างน้อย)

นี่คือ 5 วิธีคือ:

f1: เพียงแค่ใช้vector, sort+unique

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

f2: แปลงเป็นset(ใช้ constructor)

set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );

f3: แปลงเป็นset(ด้วยตนเอง)

set<int> s;
for (int i : vec)
    s.insert(i);
vec.assign( s.begin(), s.end() );

f4: แปลงเป็นunordered_set(ใช้ constructor)

unordered_set<int> s( vec.begin(), vec.end() );
vec.assign( s.begin(), s.end() );
sort( vec.begin(), vec.end() );

f5: แปลงเป็นunordered_set(ด้วยตนเอง)

unordered_set<int> s;
for (int i : vec)
    s.insert(i);
vec.assign( s.begin(), s.end() );
sort( vec.begin(), vec.end() );

ฉันทำการทดสอบโดยใช้เวกเตอร์จำนวน 100,000,000 ตัวเลือกแบบสุ่มในช่วง [1,10], [1,1000] และ [1,100000]

ผลลัพธ์ (เป็นวินาทีขนาดเล็กจะดีกว่า):

range         f1       f2       f3       f4      f5
[1,10]      1.6821   7.6804   2.8232   6.2634  0.7980
[1,1000]    5.0773  13.3658   8.2235   7.6884  1.9861
[1,100000]  8.7955  32.1148  26.5485  13.3278  3.9822

4
สำหรับจำนวนเต็มคุณสามารถใช้การเรียง radix ซึ่งเร็วกว่า std :: sort มาก
Changming Sun

2
เคล็ดลับด่วนในการใช้sortหรือuniqueวิธีการคุณต้อง#include <algorithm>
Davmrtl

3
@ ChangmingSun ฉันสงสัยว่าทำไมเครื่องมือเพิ่มประสิทธิภาพจึงล้มเหลวใน f4 ตัวเลขนั้นแตกต่างอย่างมากกับ f5 มันไม่สมเหตุสมผลเลยสำหรับฉัน
sandthorn

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

อาที่ทำให้ผมนึกถึงหนึ่งของการเจรจาก็อตต์เมเยอร์เกี่ยวกับCWUK scenerioที่มีลักษณะของ propablities จะชะลอตัวลงเป็นemplaceชนิดของการก่อสร้าง
sandthorn

57

std::unique ลบองค์ประกอบที่ซ้ำกันเฉพาะในกรณีที่เป็นประเทศเพื่อนบ้าน: คุณต้องเรียงลำดับเวกเตอร์ก่อนจึงจะทำงานได้ตามที่คุณต้องการ

std::unique ถูกกำหนดให้มีเสถียรภาพดังนั้นเวกเตอร์จะยังคงถูกจัดเรียงหลังจากรันเฉพาะบน


42

ฉันไม่แน่ใจว่าสิ่งที่คุณกำลังใช้นี้ดังนั้นฉันไม่สามารถพูดนี้ด้วย% มั่นใจ 100 แต่ตามปกติเมื่อผมคิดว่า "เรียงลำดับไม่ซ้ำกันภาชนะ" ผมคิดว่าของมาตรฐาน :: ชุด มันอาจจะเหมาะสำหรับ usecase ของคุณ:

std::set<Foo> foos(vec.begin(), vec.end()); // both sorted & unique already

มิฉะนั้นการเรียงลำดับก่อนการโทรที่ไม่ซ้ำกัน


มาถึงจุดนี้! std :: set ถูกระบุให้เป็นชุดเฉพาะที่เรียงลำดับแล้ว การใช้งานส่วนใหญ่ใช้ต้นไม้ไบนารีสั่งที่มีประสิทธิภาพหรือสิ่งที่คล้ายกัน
notnoop

+1 ความคิดของการตั้งค่าเช่นกัน ไม่ต้องการที่จะทำซ้ำคำตอบนี้
ทอม

เป็นมาตรฐาน :: ตั้งรับประกันว่าจะเรียง? มันสมเหตุสมผลแล้วในทางปฏิบัติ แต่มาตรฐานนั้นต้องการมันหรือไม่?
MadCoder

1
ใช่ดู 23.1.4.9 "คุณสมบัติพื้นฐานของตัววนซ้ำของคอนเทนเนอร์แบบเชื่อมโยงคือพวกมันวนซ้ำผ่านคอนเทนเนอร์ตามลำดับที่ไม่ได้ลงมาของคีย์โดยที่การไม่ลดระดับถูกกำหนดโดยการเปรียบเทียบที่ใช้สร้างพวกมัน"
Todd Gardner

1
@MadCoder: มันไม่จำเป็นว่า "เหมาะสม" ที่มีการใช้งานชุดในวิธีที่เรียงลำดับ นอกจากนี้ยังมีชุดการใช้งานโดยใช้ตารางแฮชซึ่งไม่ได้จัดเรียง ในความเป็นจริงคนส่วนใหญ่ชอบใช้ตารางแฮชเมื่อมี แต่หลักการตั้งชื่อใน C ++ นั้นเกิดขึ้นที่ภาชนะที่เชื่อมโยงแบบเรียงลำดับนั้นมีชื่อว่าเพียง "set" / "map" (คล้ายกับ TreeSet / TreeMap ใน Java); และคอนเทนเนอร์ที่เชื่อมโยงแบบแฮชซึ่งถูกปล่อยออกมาจากมาตรฐานเรียกว่า "hash_set" / "hash_map" (SGI STL) หรือ "unordered_set" / "unordered_map" (TR1) (คล้ายกับ HashSet และ HashMap ใน Java)
newacct

22

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


18

นี่คือเทมเพลตสำหรับคุณ:

template<typename T>
void removeDuplicates(std::vector<T>& vec)
{
    std::sort(vec.begin(), vec.end());
    vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
}

เรียกว่าชอบ:

removeDuplicates<int>(vectorname);

2
+1 Templatize ออกไป! - แต่คุณก็สามารถเขียน removeDuplicates (VEC) โดยไม่มีการระบุอย่างชัดเจนข้อโต้แย้งแม่แบบ
Faisal Vali

10
หรือดีกว่าเพียงแค่ใช้ตัววนซ้ำแบบเทมเพลตโดยตรง (เริ่มต้นและสิ้นสุด) และคุณสามารถเรียกใช้มันบนโครงสร้างอื่น ๆ นอกเหนือจากเวกเตอร์
Kyle Ryan

เทมเพลต Hells ใช่แล้ว! การแก้ไขอย่างรวดเร็วสำหรับรายการขนาดเล็กสไตล์ STL เต็มรูปแบบ +1 ขอบคุณ
QuantumKarl

@Kyle - เฉพาะบนคอนเทนเนอร์อื่นที่มีerase()เมธอดมิฉะนั้นคุณจะต้องส่งคืนตัววนซ้ำสุดท้ายใหม่และให้รหัสการโทรตัดทอนคอนเทนเนอร์
Toby Speight

8

ประสิทธิภาพเป็นแนวคิดที่ซับซ้อน มีข้อควรพิจารณาเกี่ยวกับเวลาและพื้นที่รวมถึงการวัดทั่วไป (ที่คุณจะได้รับคำตอบที่คลุมเครือเช่น O (n)) กับข้อเสนอที่เฉพาะเจาะจง (เช่นการเรียงลำดับฟองอาจเร็วกว่าการเรียงแบบเร็วขึ้นอยู่กับลักษณะการป้อนข้อมูล)

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

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


อะไรจะให้แผนที่ / ลดความสามารถ สิ่งเดียวที่ฉันคิดได้ก็คือการเรียงลำดับการผสานแบบกระจายและคุณยังสามารถใช้เพียงหนึ่งเธรดในการผสานขั้นสุดท้าย
Zan Lynx

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

7

คุณต้องเรียงลำดับก่อนที่จะโทรuniqueเพราะuniqueจะลบรายการซ้ำที่อยู่ติดกัน

แก้ไข: 38 วินาที ...


7

uniqueลบองค์ประกอบที่ซ้ำกันตามลำดับเท่านั้น (ซึ่งจำเป็นต่อการรันในเวลาเชิงเส้น) ดังนั้นคุณควรดำเนินการเรียงลำดับก่อน uniqueมันจะยังคงเรียงหลังจากการเรียกร้องให้


7

หากคุณไม่ต้องการเปลี่ยนลำดับขององค์ประกอบคุณสามารถลองวิธีนี้ได้:

template <class T>
void RemoveDuplicatesInVector(std::vector<T> & vec)
{
    set<T> values;
    vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const T & value) { return !values.insert(value).second; }), vec.end());
}

อาจใช้ unordered_set แทนการตั้งค่า (และเพิ่ม :: remove_erase_if ถ้ามี)
gast128

4

สมมติว่าaเป็นเวกเตอร์ให้ลบคำซ้ำที่ต่อเนื่องกันโดยใช้

a.erase(unique(a.begin(),a.end()),a.end());วิ่งในO (n)เวลา


1
ซ้ำซ้อนที่ต่อเนื่องกัน ตกลงดังนั้นมันต้องเป็นstd::sortครั้งแรก
v.oddou

2

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


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

@Pate คุณถูกต้อง มันไม่จำเป็นต้องมีอย่างใดอย่างหนึ่ง จะลบรายการที่อยู่ติดกัน
Bill Lynch

หากคุณมีคอนเทนเนอร์ที่อาจมีการทำซ้ำและคุณต้องการคอนเทนเนอร์ที่ไม่มีค่าซ้ำกันทุกที่ในคอนเทนเนอร์จากนั้นคุณต้องเรียงลำดับคอนเทนเนอร์ก่อนจากนั้นส่งผ่านไปยังที่ไม่ซ้ำกันแล้วใช้การลบเพื่อลบรายการที่ซ้ำกัน . หากคุณต้องการลบรายการที่ซ้ำกันที่อยู่ติดกันคุณจะไม่ต้องเรียงลำดับคอนเทนเนอร์ แต่คุณจะจบลงด้วยค่าที่ซ้ำกัน: 1 2 2 3 2 4 2 5 2 จะถูกเปลี่ยนเป็น 1 2 3 2 4 2 5 2 ถ้าผ่านไปไม่ซ้ำกันโดยไม่ต้องเรียงลำดับ 1 2 3 4 5 ถ้าเรียงลำดับผ่านไปไม่ซ้ำกันและลบ .
Max Lybbert

2

วิธีมาตรฐานที่แนะนำโดย Nate Kohl เพียงแค่ใช้ vector, sort + unique:

sort( vec.begin(), vec.end() );
vec.erase( unique( vec.begin(), vec.end() ), vec.end() );

ไม่ทำงานกับเวกเตอร์ของพอยน์เตอร์

ดูตัวอย่างนี้อย่างระมัดระวังใน cplusplus.comcplusplus.com

ในตัวอย่างของพวกเขา "ที่ซ้ำกันที่เรียกว่าซ้ำกัน" ที่ย้ายไปยังจุดสิ้นสุดจริง ๆ แล้วปรากฏเป็น (ค่าที่ไม่ได้กำหนด) เนื่องจาก "รายการที่เรียกว่าซ้ำกัน" เหล่านั้นเป็น SOMETIMES "องค์ประกอบพิเศษ" และ SOMETIMES มี "องค์ประกอบที่ขาดหายไป" ซึ่งอยู่ในเวกเตอร์ดั้งเดิม

เกิดปัญหาเมื่อใช้งาน std::unique()กับเวกเตอร์ของพอยน์เตอร์ไปยังวัตถุ (หน่วยความจำรั่วการอ่านข้อมูลที่ไม่ถูกต้องจาก HEAP, การปลดปล่อยซ้ำซึ่งทำให้การแบ่งเซ็กเมนต์ผิดพลาด ฯลฯ )

นี่คือวิธีแก้ไขปัญหาของฉัน: แทนที่std::unique()ด้วยptgi::unique()ด้วย

ดูไฟล์ ptgi_unique.hpp ด้านล่าง:

// ptgi::unique()
//
// Fix a problem in std::unique(), such that none of the original elts in the collection are lost or duplicate.
// ptgi::unique() has the same interface as std::unique()
//
// There is the 2 argument version which calls the default operator== to compare elements.
//
// There is the 3 argument version, which you can pass a user defined functor for specialized comparison.
//
// ptgi::unique() is an improved version of std::unique() which doesn't looose any of the original data
// in the collection, nor does it create duplicates.
//
// After ptgi::unique(), every old element in the original collection is still present in the re-ordered collection,
// except that duplicates have been moved to a contiguous range [dupPosition, last) at the end.
//
// Thus on output:
//  [begin, dupPosition) range are unique elements.
//  [dupPosition, last) range are duplicates which can be removed.
// where:
//  [] means inclusive, and
//  () means exclusive.
//
// In the original std::unique() non-duplicates at end are moved downward toward beginning.
// In the improved ptgi:unique(), non-duplicates at end are swapped with duplicates near beginning.
//
// In addition if you have a collection of ptrs to objects, the regular std::unique() will loose memory,
// and can possibly delete the same pointer multiple times (leading to SEGMENTATION VIOLATION on Linux machines)
// but ptgi::unique() won't.  Use valgrind(1) to find such memory leak problems!!!
//
// NOTE: IF you have a vector of pointers, that is, std::vector<Object*>, then upon return from ptgi::unique()
// you would normally do the following to get rid of the duplicate objects in the HEAP:
//
//  // delete objects from HEAP
//  std::vector<Object*> objects;
//  for (iter = dupPosition; iter != objects.end(); ++iter)
//  {
//      delete (*iter);
//  }
//
//  // shrink the vector. But Object * pointers are NOT followed for duplicate deletes, this shrinks the vector.size())
//  objects.erase(dupPosition, objects.end));
//
// NOTE: But if you have a vector of objects, that is: std::vector<Object>, then upon return from ptgi::unique(), it
// suffices to just call vector:erase(, as erase will automatically call delete on each object in the
// [dupPosition, end) range for you:
//
//  std::vector<Object> objects;
//  objects.erase(dupPosition, last);
//
//==========================================================================================================
// Example of differences between std::unique() vs ptgi::unique().
//
//  Given:
//      int data[] = {10, 11, 21};
//
//  Given this functor: ArrayOfIntegersEqualByTen:
//      A functor which compares two integers a[i] and a[j] in an int a[] array, after division by 10:
//  
//  // given an int data[] array, remove consecutive duplicates from it.
//  // functor used for std::unique (BUGGY) or ptgi::unique(IMPROVED)
//
//  // Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
//  // Hence 50..59 are equal, 60..69 are equal, etc.
//  struct ArrayOfIntegersEqualByTen: public std::equal_to<int>
//  {
//      bool operator() (const int& arg1, const int& arg2) const
//      {
//          return ((arg1/10) == (arg2/10));
//      }
//  };
//  
//  Now, if we call (problematic) std::unique( data, data+3, ArrayOfIntegersEqualByTen() );
//  
//  TEST1: BEFORE UNIQ: 10,11,21
//  TEST1: AFTER UNIQ: 10,21,21
//  DUP_INX=2
//  
//      PROBLEM: 11 is lost, and extra 21 has been added.
//  
//  More complicated example:
//  
//  TEST2: BEFORE UNIQ: 10,20,21,22,30,31,23,24,11
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,23,24,11
//  DUP_INX=5
//  
//      Problem: 21 and 22 are deleted.
//      Problem: 11 and 23 are duplicated.
//  
//  
//  NOW if ptgi::unique is called instead of std::unique, both problems go away:
//  
//  DEBUG: TEST1: NEW_WAY=1
//  TEST1: BEFORE UNIQ: 10,11,21
//  TEST1: AFTER UNIQ: 10,21,11
//  DUP_INX=2
//  
//  DEBUG: TEST2: NEW_WAY=1
//  TEST2: BEFORE UNIQ: 10,20,21,22,30,31,23,24,11
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,22,24,21
//  DUP_INX=5
//
//  @SEE: look at the "case study" below to understand which the last "AFTER UNIQ" results with that order:
//  TEST2: AFTER UNIQ: 10,20,30,23,11,31,22,24,21
//
//==========================================================================================================
// Case Study: how ptgi::unique() works:
//  Remember we "remove adjacent duplicates".
//  In this example, the input is NOT fully sorted when ptgi:unique() is called.
//
//  I put | separatators, BEFORE UNIQ to illustrate this
//  10  | 20,21,22 |  30,31 |  23,24 | 11
//
//  In example above, 20, 21, 22 are "same" since dividing by 10 gives 2 quotient.
//  And 30,31 are "same", since /10 quotient is 3.
//  And 23, 24 are same, since /10 quotient is 2.
//  And 11 is "group of one" by itself.
//  So there are 5 groups, but the 4th group (23, 24) happens to be equal to group 2 (20, 21, 22)
//  So there are 5 groups, and the 5th group (11) is equal to group 1 (10)
//
//  R = result
//  F = first
//
//  10, 20, 21, 22, 30, 31, 23, 24, 11
//  R    F
//
//  10 is result, and first points to 20, and R != F (10 != 20) so bump R:
//       R
//       F
//
//  Now we hits the "optimized out swap logic".
//  (avoid swap because R == F)
//
//  // now bump F until R != F (integer division by 10)
//  10, 20, 21, 22, 30, 31, 23, 24, 11
//       R   F              // 20 == 21 in 10x
//       R       F              // 20 == 22 in 10x
//       R           F          // 20 != 30, so we do a swap of ++R and F
//  (Now first hits 21, 22, then finally 30, which is different than R, so we swap bump R to 21 and swap with  30)
//  10, 20, 30, 22, 21, 31, 23, 24, 11  // after R & F swap (21 and 30)
//           R       F 
//
//  10, 20, 30, 22, 21, 31, 23, 24, 11
//           R          F           // bump F to 31, but R and F are same (30 vs 31)
//           R               F      // bump F to 23, R != F, so swap ++R with F
//  10, 20, 30, 22, 21, 31, 23, 24, 11
//                  R           F       // bump R to 22
//  10, 20, 30, 23, 21, 31, 22, 24, 11  // after the R & F swap (22 & 23 swap)
//                  R            F      // will swap 22 and 23
//                  R                F      // bump F to 24, but R and F are same in 10x
//                  R                    F  // bump F, R != F, so swap ++R  with F
//                      R                F  // R and F are diff, so swap ++R  with F (21 and 11)
//  10, 20, 30, 23, 11, 31, 22, 24, 21
//                      R                F  // aftter swap of old 21 and 11
//                      R                  F    // F now at last(), so loop terminates
//                          R               F   // bump R by 1 to point to dupPostion (first duplicate in range)
//
//  return R which now points to 31
//==========================================================================================================
// NOTES:
// 1) the #ifdef IMPROVED_STD_UNIQUE_ALGORITHM documents how we have modified the original std::unique().
// 2) I've heavily unit tested this code, including using valgrind(1), and it is *believed* to be 100% defect-free.
//
//==========================================================================================================
// History:
//  130201  dpb dbednar@ptgi.com created
//==========================================================================================================

#ifndef PTGI_UNIQUE_HPP
#define PTGI_UNIQUE_HPP

// Created to solve memory leak problems when calling std::unique() on a vector<Route*>.
// Memory leaks discovered with valgrind and unitTesting.


#include <algorithm>        // std::swap

// instead of std::myUnique, call this instead, where arg3 is a function ptr
//
// like std::unique, it puts the dups at the end, but it uses swapping to preserve original
// vector contents, to avoid memory leaks and duplicate pointers in vector<Object*>.

#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
#error the #ifdef for IMPROVED_STD_UNIQUE_ALGORITHM was defined previously.. Something is wrong.
#endif

#undef IMPROVED_STD_UNIQUE_ALGORITHM
#define IMPROVED_STD_UNIQUE_ALGORITHM

// similar to std::unique, except that this version swaps elements, to avoid
// memory leaks, when vector contains pointers.
//
// Normally the input is sorted.
// Normal std::unique:
// 10 20 20 20 30   30 20 20 10
// a  b  c  d  e    f  g  h  i
//
// 10 20 30 20 10 | 30 20 20 10
// a  b  e  g  i    f  g  h  i
//
// Now GONE: c, d.
// Now DUPS: g, i.
// This causes memory leaks and segmenation faults due to duplicate deletes of same pointer!


namespace ptgi {

// Return the position of the first in range of duplicates moved to end of vector.
//
// uses operator==  of class for comparison
//
// @param [first, last) is a range to find duplicates within.
//
// @return the dupPosition position, such that [dupPosition, end) are contiguous
// duplicate elements.
// IF all items are unique, then it would return last.
//
template <class ForwardIterator>
ForwardIterator unique( ForwardIterator first, ForwardIterator last)
{
    // compare iterators, not values
    if (first == last)
        return last;

    // remember the current item that we are looking at for uniqueness
    ForwardIterator result = first;

    // result is slow ptr where to store next unique item
    // first is  fast ptr which is looking at all elts

    // the first iterator moves over all elements [begin+1, end).
    // while the current item (result) is the same as all elts
    // to the right, (first) keeps going, until you find a different
    // element pointed to by *first.  At that time, we swap them.

    while (++first != last)
    {
        if (!(*result == *first))
        {
#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
            // inc result, then swap *result and *first

//          THIS IS WHAT WE WANT TO DO.
//          BUT THIS COULD SWAP AN ELEMENT WITH ITSELF, UNCECESSARILY!!!
//          std::swap( *first, *(++result));

            // BUT avoid swapping with itself when both iterators are the same
            ++result;
            if (result != first)
                std::swap( *first, *result);
#else
            // original code found in std::unique()
            // copies unique down
            *(++result) = *first;
#endif
        }
    }

    return ++result;
}

template <class ForwardIterator, class BinaryPredicate>
ForwardIterator unique( ForwardIterator first, ForwardIterator last, BinaryPredicate pred)
{
    if (first == last)
        return last;

    // remember the current item that we are looking at for uniqueness
    ForwardIterator result = first;

    while (++first != last)
    {
        if (!pred(*result,*first))
        {
#ifdef IMPROVED_STD_UNIQUE_ALGORITHM
            // inc result, then swap *result and *first

//          THIS COULD SWAP WITH ITSELF UNCECESSARILY
//          std::swap( *first, *(++result));
//
            // BUT avoid swapping with itself when both iterators are the same
            ++result;
            if (result != first)
                std::swap( *first, *result);

#else
            // original code found in std::unique()
            // copies unique down
            // causes memory leaks, and duplicate ptrs
            // and uncessarily moves in place!
            *(++result) = *first;
#endif
        }
    }

    return ++result;
}

// from now on, the #define is no longer needed, so get rid of it
#undef IMPROVED_STD_UNIQUE_ALGORITHM

} // end ptgi:: namespace

#endif

และนี่คือโปรแกรมทดสอบ UNIT ที่ฉันใช้ทดสอบ:

// QUESTION: in test2, I had trouble getting one line to compile,which was caused  by the declaration of operator()
// in the equal_to Predicate.  I'm not sure how to correctly resolve that issue.
// Look for //OUT lines
//
// Make sure that NOTES in ptgi_unique.hpp are correct, in how we should "cleanup" duplicates
// from both a vector<Integer> (test1()) and vector<Integer*> (test2).
// Run this with valgrind(1).
//
// In test2(), IF we use the call to std::unique(), we get this problem:
//
//  [dbednar@ipeng8 TestSortRoutes]$ ./Main7
//  TEST2: ORIG nums before UNIQUE: 10, 20, 21, 22, 30, 31, 23, 24, 11
//  TEST2: modified nums AFTER UNIQUE: 10, 20, 30, 23, 11, 31, 23, 24, 11
//  INFO: dupInx=5
//  TEST2: uniq = 10
//  TEST2: uniq = 20
//  TEST2: uniq = 30
//  TEST2: uniq = 33427744
//  TEST2: uniq = 33427808
//  Segmentation fault (core dumped)
//
// And if we run valgrind we seen various error about "read errors", "mismatched free", "definitely lost", etc.
//
//  valgrind --leak-check=full ./Main7
//  ==359== Memcheck, a memory error detector
//  ==359== Command: ./Main7
//  ==359== Invalid read of size 4
//  ==359== Invalid free() / delete / delete[]
//  ==359== HEAP SUMMARY:
//  ==359==     in use at exit: 8 bytes in 2 blocks
//  ==359== LEAK SUMMARY:
//  ==359==    definitely lost: 8 bytes in 2 blocks
// But once we replace the call in test2() to use ptgi::unique(), all valgrind() error messages disappear.
//
// 130212   dpb dbednar@ptgi.com created
// =========================================================================================================

#include <iostream> // std::cout, std::cerr
#include <string>
#include <vector>   // std::vector
#include <sstream>  // std::ostringstream
#include <algorithm>    // std::unique()
#include <functional>   // std::equal_to(), std::binary_function()
#include <cassert>  // assert() MACRO

#include "ptgi_unique.hpp"  // ptgi::unique()



// Integer is small "wrapper class" around a primitive int.
// There is no SETTER, so Integer's are IMMUTABLE, just like in JAVA.

class Integer
{
private:
    int num;
public:

    // default CTOR: "Integer zero;"
    // COMPRENSIVE CTOR:  "Integer five(5);"
    Integer( int num = 0 ) :
        num(num)
    {
    }

    // COPY CTOR
    Integer( const Integer& rhs) :
        num(rhs.num)
    {
    }

    // assignment, operator=, needs nothing special... since all data members are primitives

    // GETTER for 'num' data member
    // GETTER' are *always* const
    int getNum() const
    {
        return num;
    }   

    // NO SETTER, because IMMUTABLE (similar to Java's Integer class)

    // @return "num"
    // NB: toString() should *always* be a const method
    //
    // NOTE: it is probably more efficient to call getNum() intead
    // of toString() when printing a number:
    //
    // BETTER to do this:
    //  Integer five(5);
    //  std::cout << five.getNum() << "\n"
    // than this:
    //  std::cout << five.toString() << "\n"

    std::string toString() const
    {
        std::ostringstream oss;
        oss << num;
        return oss.str();
    }
};

// convenience typedef's for iterating over std::vector<Integer>
typedef std::vector<Integer>::iterator      IntegerVectorIterator;
typedef std::vector<Integer>::const_iterator    ConstIntegerVectorIterator;

// convenience typedef's for iterating over std::vector<Integer*>
typedef std::vector<Integer*>::iterator     IntegerStarVectorIterator;
typedef std::vector<Integer*>::const_iterator   ConstIntegerStarVectorIterator;

// functor used for std::unique or ptgi::unique() on a std::vector<Integer>
// Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
// Hence 50..59 are equal, 60..69 are equal, etc.
struct IntegerEqualByTen: public std::equal_to<Integer>
{
    bool operator() (const Integer& arg1, const Integer& arg2) const
    {
        return ((arg1.getNum()/10) == (arg2.getNum()/10));
    }
};

// functor used for std::unique or ptgi::unique on a std::vector<Integer*>
// Two numbers equal if, when divided by 10 (integer division), the quotients are the same.
// Hence 50..59 are equal, 60..69 are equal, etc.
struct IntegerEqualByTenPointer: public std::equal_to<Integer*>
{
    // NB: the Integer*& looks funny to me!
    // TECHNICAL PROBLEM ELSEWHERE so had to remove the & from *&
//OUT   bool operator() (const Integer*& arg1, const Integer*& arg2) const
//
    bool operator() (const Integer* arg1, const Integer* arg2) const
    {
        return ((arg1->getNum()/10) == (arg2->getNum()/10));
    }
};

void test1();
void test2();
void printIntegerStarVector( const std::string& msg, const std::vector<Integer*>& nums );

int main()
{
    test1();
    test2();
    return 0;
}

// test1() uses a vector<Object> (namely vector<Integer>), so there is no problem with memory loss
void test1()
{
    int data[] = { 10, 20, 21, 22, 30, 31, 23, 24, 11};

    // turn C array into C++ vector
    std::vector<Integer> nums(data, data+9);

    // arg3 is a functor
    IntegerVectorIterator dupPosition = ptgi::unique( nums.begin(), nums.end(), IntegerEqualByTen() );

    nums.erase(dupPosition, nums.end());

    nums.erase(nums.begin(), dupPosition);
}

//==================================================================================
// test2() uses a vector<Integer*>, so after ptgi:unique(), we have to be careful in
// how we eliminate the duplicate Integer objects stored in the heap.
//==================================================================================
void test2()
{
    int data[] = { 10, 20, 21, 22, 30, 31, 23, 24, 11};

    // turn C array into C++ vector of Integer* pointers
    std::vector<Integer*> nums;

    // put data[] integers into equivalent Integer* objects in HEAP
    for (int inx = 0; inx < 9; ++inx)
    {
        nums.push_back( new Integer(data[inx]) );
    }

    // print the vector<Integer*> to stdout
    printIntegerStarVector( "TEST2: ORIG nums before UNIQUE", nums );

    // arg3 is a functor
#if 1
    // corrected version which fixes SEGMENTATION FAULT and all memory leaks reported by valgrind(1)
    // I THINK we want to use new C++11 cbegin() and cend(),since the equal_to predicate is passed "Integer *&"

//  DID NOT COMPILE
//OUT   IntegerStarVectorIterator dupPosition = ptgi::unique( const_cast<ConstIntegerStarVectorIterator>(nums.begin()), const_cast<ConstIntegerStarVectorIterator>(nums.end()), IntegerEqualByTenPointer() );

    // DID NOT COMPILE when equal_to predicate declared "Integer*& arg1, Integer*&  arg2"
//OUT   IntegerStarVectorIterator dupPosition = ptgi::unique( const_cast<nums::const_iterator>(nums.begin()), const_cast<nums::const_iterator>(nums.end()), IntegerEqualByTenPointer() );


    // okay when equal_to predicate declared "Integer* arg1, Integer*  arg2"
    IntegerStarVectorIterator dupPosition = ptgi::unique(nums.begin(), nums.end(), IntegerEqualByTenPointer() );
#else
    // BUGGY version that causes SEGMENTATION FAULT and valgrind(1) errors
    IntegerStarVectorIterator dupPosition = std::unique( nums.begin(), nums.end(), IntegerEqualByTenPointer() );
#endif

    printIntegerStarVector( "TEST2: modified nums AFTER UNIQUE", nums );
    int dupInx = dupPosition - nums.begin();
    std::cout << "INFO: dupInx=" << dupInx <<"\n";

    // delete the dup Integer* objects in the [dupPosition, end] range
    for (IntegerStarVectorIterator iter = dupPosition; iter != nums.end(); ++iter)
    {
        delete (*iter);
    }

    // shrink the vector
    // NB: the Integer* ptrs are NOT followed by vector::erase()
    nums.erase(dupPosition, nums.end());


    // print the uniques, by following the iter to the Integer* pointer
    for (IntegerStarVectorIterator iter = nums.begin(); iter != nums.end();  ++iter)
    {
        std::cout << "TEST2: uniq = " << (*iter)->getNum() << "\n";
    }

    // remove the unique objects from heap
    for (IntegerStarVectorIterator iter = nums.begin(); iter != nums.end();  ++iter)
    {
        delete (*iter);
    }

    // shrink the vector
    nums.erase(nums.begin(), nums.end());

    // the vector should now be completely empty
    assert( nums.size() == 0);
}

//@ print to stdout the string: "info_msg: num1, num2, .... numN\n"
void printIntegerStarVector( const std::string& msg, const std::vector<Integer*>& nums )
{
    std::cout << msg << ": ";
    int inx = 0;
    ConstIntegerStarVectorIterator  iter;

    // use const iterator and const range!
    // NB: cbegin() and cend() not supported until LATER (c++11)
    for (iter = nums.begin(), inx = 0; iter != nums.end(); ++iter, ++inx)
    {
        // output a comma seperator *AFTER* first
        if (inx > 0)
            std::cout << ", ";

        // call Integer::toString()
        std::cout << (*iter)->getNum();     // send int to stdout
//      std::cout << (*iter)->toString();   // also works, but is probably slower

    }

    // in conclusion, add newline
    std::cout << "\n";
}

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

ไม่แน่ใจว่าฉันเข้าใจประเด็นของคุณหรือไม่ ใช้กรณีแบบง่ายของเวกเตอร์ <int *> โดยที่ 4 พอยน์เตอร์ชี้ไปที่จำนวนเต็ม {1, 2 2, 3} มันเรียงลำดับแล้ว แต่หลังจากที่คุณเรียก std :: unique แล้ว 4 พอยน์เตอร์จะเป็นพอยน์เตอร์สำหรับจำนวนเต็ม {1, 2, 3, 3} ตอนนี้คุณมีตัวชี้ที่เหมือนกันสองตัวถึง 3 ดังนั้นหากคุณเรียกลบมันเป็นการลบที่ซ้ำกัน ไม่ดี! ประการที่สองสังเกตว่า 2 2 คือการขาดหายไปซึ่งเป็นหน่วยความจำรั่ว
โจ

kccqzy, ริโปรแกรมตัวอย่างสำหรับคุณที่จะเข้าใจคำตอบของฉันดีกว่า:
joe

@ โจ: แม้ว่าหลังจากที่std::uniqueคุณมี [1, 2, 3, 2] คุณไม่สามารถโทรลบเลข2 ได้เพราะจะทำให้ตัวชี้ห้อยอยู่ที่ 2! => อย่าเรียกลบในองค์ประกอบระหว่างnewEnd = std::uniqueและstd::endตามที่คุณยังมีตัวชี้ไปยังองค์ประกอบเหล่านี้ใน[std::begin, newEnd)!
MFH

2
@ArneVogel: สำหรับค่าเล็กน้อยของ "ทำงานได้ดี" บางที มันค่อนข้างไม่มีจุดหมายที่จะเรียกuniqueใช้ a vector<unique_ptr<T>>เนื่องจากค่าซ้ำซ้อนเท่านั้นที่เวกเตอร์สามารถมีnullptrได้
Ben Voigt

2

ด้วยไลบรารี Ranges (มาใน C ++ 20) คุณสามารถใช้

action::unique(vec);

โปรดทราบว่าจริง ๆ แล้วมันลบองค์ประกอบที่ซ้ำกันไม่เพียง แต่ย้ายพวกเขา


1

เกี่ยวกับมาตรฐาน alexK7 ฉันลองพวกเขาและได้ผลลัพธ์ที่คล้ายกัน แต่เมื่อช่วงของค่าคือ 1 ล้านกรณีที่ใช้ std :: sort (f1) และใช้ std :: unordered_set (f5) สร้างเวลาที่คล้ายกัน เมื่อช่วงของค่าคือ 10 ล้าน f1 เร็วกว่า f5

หากช่วงของค่าถูก จำกัด และค่าไม่ได้ลงนาม int ก็เป็นไปได้ที่จะใช้ std :: vector ขนาดที่สอดคล้องกับช่วงที่กำหนด นี่คือรหัส:

void DeleteDuplicates_vector_bool(std::vector<unsigned>& v, unsigned range_size)
{
    std::vector<bool> v1(range_size);
    for (auto& x: v)
    {
       v1[x] = true;    
    }
    v.clear();

    unsigned count = 0;
    for (auto& x: v1)
    {
        if (x)
        {
            v.push_back(count);
        }
        ++count;
    }
}


1

หากคุณกำลังมองหาประสิทธิภาพและการใช้std::vectorงานฉันขอแนะนำลิงค์ที่ให้เอกสารนี้

std::vector<int> myvector{10,20,20,20,30,30,20,20,10};             // 10 20 20 20 30 30 20 20 10
std::sort(myvector.begin(), myvector.end() );
const auto& it = std::unique (myvector.begin(), myvector.end());   // 10 20 30 ?  ?  ?  ?  ?  ?
                                                                   //          ^
myvector.resize( std::distance(myvector.begin(),it) ); // 10 20 30

cplusplus.com ไม่ได้เป็นเอกสารที่เป็นทางการ แต่อย่างใด
Ilya Popov

0
std::set<int> s;
std::for_each(v.cbegin(), v.cend(), [&s](int val){s.insert(val);});
v.clear();
std::copy(s.cbegin(), s.cend(), v.cbegin());

1
อาจปรับขนาดเวกเตอร์หลังจากล้างมันเพื่อให้มีการจัดสรรหน่วยความจำเพียง 1 เมื่อสร้างเวกเตอร์ อาจต้องการ std :: move แทน std :: copy เพื่อย้าย ints ไปยัง vector แทนที่จะคัดลอกมันเนื่องจากชุดจะไม่ต้องการในภายหลัง
YoungJohn

0

หากคุณไม่ต้องการแก้ไขเวกเตอร์ (ลบ, เรียงลำดับ) จากนั้นคุณสามารถใช้ห้องสมุดนิวตัน , ในห้องสมุดย่อยอัลกอริทึมมีการเรียกใช้ฟังก์ชัน, copy_single

template <class INPUT_ITERATOR, typename T>
    void copy_single( INPUT_ITERATOR first, INPUT_ITERATOR last, std::vector<T> &v )

คุณสามารถ:

std::vector<TYPE> copy; // empty vector
newton::copy_single(first, last, copy);

โดยที่copyคือเวกเตอร์ที่คุณต้องการpush_backสำเนาขององค์ประกอบที่ไม่ซ้ำกัน แต่อย่าลืมว่าคุณสร้างองค์ประกอบของpush_backและคุณจะไม่สร้างเวกเตอร์ใหม่

อย่างไรก็ตามสิ่งนี้เร็วกว่าเพราะคุณไม่ได้ลบ () องค์ประกอบ (ซึ่งใช้เวลานานยกเว้นเมื่อคุณ pop_back () เนื่องจากการกำหนดใหม่)

ฉันทำการทดลองและเร็วขึ้น

นอกจากนี้คุณสามารถใช้:

std::vector<TYPE> copy; // empty vector
newton::copy_single(first, last, copy);
original = copy;

บางครั้งก็ยังเร็วกว่า


1
unique_copyฟังก์ชั่นนี้มีอยู่ในห้องสมุดมาตรฐาน
LF

0

โค้ดที่เข้าใจได้มากขึ้นจาก: https://en.cppreference.com/w/cpp/algorithm/unique

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <cctype>

int main() 
{
    // remove duplicate elements
    std::vector<int> v{1,2,3,1,2,3,3,4,5,4,5,6,7};
    std::sort(v.begin(), v.end()); // 1 1 2 2 3 3 3 4 4 5 5 6 7 
    auto last = std::unique(v.begin(), v.end());
    // v now holds {1 2 3 4 5 6 7 x x x x x x}, where 'x' is indeterminate
    v.erase(last, v.end()); 
    for (int i : v)
      std::cout << i << " ";
    std::cout << "\n";
}

ouput:

1 2 3 4 5 6 7

0
void removeDuplicates(std::vector<int>& arr) {
    for (int i = 0; i < arr.size(); i++)
    {
        for (int j = i + 1; j < arr.size(); j++)
        {
            if (arr[i] > arr[j])
            {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
    std::vector<int> y;
    int x = arr[0];
    int i = 0;
    while (i < arr.size())
    {
        if (x != arr[i])
        {
            y.push_back(x);
            x = arr[i];
        }
        i++;
        if (i == arr.size())
            y.push_back(arr[i - 1]);
    }
    arr = y;
}

2
ยินดีต้อนรับสู่ StackOverflow! โปรดแก้ไขคำถามของคุณเพื่อเพิ่มคำอธิบายเกี่ยวกับวิธีการทำงานของรหัสของคุณและทำไมจึงเทียบเท่าหรือดีกว่าคำตอบอื่น ๆ คำถามนี้มีอายุมากกว่าสิบปีแล้วและมีคำตอบที่ดีและมีคำอธิบายมากมายอยู่แล้ว หากไม่มีคำอธิบายของคุณก็ไม่เป็นประโยชน์และมีโอกาสดีที่จะถูกลดระดับหรือลบออก
Das_Geek

-1

นี่คือตัวอย่างของปัญหาการลบที่ซ้ำกันที่เกิดขึ้นกับ std :: unique () บนเครื่อง LINUX โปรแกรมขัดข้อง อ่านความคิดเห็นเพื่อดูรายละเอียด

// Main10.cpp
//
// Illustration of duplicate delete and memory leak in a vector<int*> after calling std::unique.
// On a LINUX machine, it crashes the progam because of the duplicate delete.
//
// INPUT : {1, 2, 2, 3}
// OUTPUT: {1, 2, 3, 3}
//
// The two 3's are actually pointers to the same 3 integer in the HEAP, which is BAD
// because if you delete both int* pointers, you are deleting the same memory
// location twice.
//
//
// Never mind the fact that we ignore the "dupPosition" returned by std::unique(),
// but in any sensible program that "cleans up after istelf" you want to call deletex
// on all int* poitners to avoid memory leaks.
//
//
// NOW IF you replace std::unique() with ptgi::unique(), all of the the problems disappear.
// Why? Because ptgi:unique merely reshuffles the data:
// OUTPUT: {1, 2, 3, 2}
// The ptgi:unique has swapped the last two elements, so all of the original elements in
// the INPUT are STILL in the OUTPUT.
//
// 130215   dbednar@ptgi.com
//============================================================================

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

#include "ptgi_unique.hpp"

// functor used by std::unique to remove adjacent elts from vector<int*>
struct EqualToVectorOfIntegerStar: public std::equal_to<int *>
{
    bool operator() (const int* arg1, const int* arg2) const
    {
        return (*arg1 == *arg2);
    }
};

void printVector( const std::string& msg, const std::vector<int*>& vnums);

int main()
{
    int inums [] = { 1, 2, 2, 3 };
    std::vector<int*> vnums;

    // convert C array into vector of pointers to integers
    for (size_t inx = 0; inx < 4; ++ inx)
        vnums.push_back( new int(inums[inx]) );

    printVector("BEFORE UNIQ", vnums);

    // INPUT : 1, 2A, 2B, 3
    std::unique( vnums.begin(), vnums.end(), EqualToVectorOfIntegerStar() );
    // OUTPUT: 1, 2A, 3, 3 }
    printVector("AFTER  UNIQ", vnums);

    // now we delete 3 twice, and we have a memory leak because 2B is not deleted.
    for (size_t inx = 0; inx < vnums.size(); ++inx)
    {
        delete(vnums[inx]);
    }
}

// print a line of the form "msg: 1,2,3,..,5,6,7\n", where 1..7 are the numbers in vnums vector
// PS: you may pass "hello world" (const char *) because of implicit (automatic) conversion
// from "const char *" to std::string conversion.

void printVector( const std::string& msg, const std::vector<int*>& vnums)
{
    std::cout << msg << ": ";

    for (size_t inx = 0; inx < vnums.size(); ++inx)
    {
        // insert comma separator before current elt, but ONLY after first elt
        if (inx > 0)
            std::cout << ",";
        std::cout << *vnums[inx];

    }
    std::cout << "\n";
}

PS: ฉันก็วิ่ง "valgrind ./Main10" และ valgrind ไม่พบปัญหา ฉันขอแนะนำโปรแกรมเมอร์ C ++ ทุกคนที่ใช้ LINUX เพื่อใช้เครื่องมือที่มีประโยชน์มากนี้โดยเฉพาะถ้าคุณกำลังเขียนแอปพลิเคชันแบบเรียลไทม์ที่ต้องเรียกใช้ 24x7 และไม่เคยรั่วหรือผิดพลาด!
joe

หัวใจของปัญหากับ std :: unique สามารถสรุปได้โดยข้อความนี้ "std :: unique return return ซ้ำในสถานะที่ไม่ระบุ" !!!!! ทำไมคณะกรรมการมาตรฐานทำเช่นนี้ฉันจะไม่มีทางรู้ กระทำการสมาชิก .. ความคิดเห็นใด ๆ ???
joe

1
ใช่ "std :: unique return return ซ้ำกันในสถานะที่ไม่ระบุ" ดังนั้นอย่าพึ่งอาร์เรย์ที่ "ไม่ซ้ำ" เพื่อจัดการหน่วยความจำด้วยตนเอง! วิธีที่ง่ายที่สุดในการทำเช่นนี้คือการใช้ std :: unique_ptr แทนที่จะเป็นตัวชี้แบบดิบ
alexk7

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

-2
void EraseVectorRepeats(vector <int> & v){ 
TOP:for(int y=0; y<v.size();++y){
        for(int z=0; z<v.size();++z){
            if(y==z){ //This if statement makes sure the number that it is on is not erased-just skipped-in order to keep only one copy of a repeated number
                continue;}
            if(v[y]==v[z]){
                v.erase(v.begin()+z); //whenever a number is erased the function goes back to start of the first loop because the size of the vector changes
            goto TOP;}}}}

นี่คือฟังก์ชั่นที่ฉันสร้างขึ้นซึ่งคุณสามารถใช้เพื่อลบซ้ำ ไฟล์ส่วนหัวที่จำเป็นเป็นเพียงและ<iostream><vector>

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