อัลกอริทึมแบบใดที่ต้องใช้ชุด?


10

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

โดยพื้นฐาน: อัลกอริทึมชนิดใดที่ต้องมีชุดและไม่ควรทำกับประเภทคอนเทนเนอร์อื่น ๆ ?


2
คุณจะเจาะจงมากขึ้นเกี่ยวกับสิ่งที่คุณหมายถึงเมื่อคุณใช้คำว่า "set" คุณหมายถึงชุดC ++ หรือไม่?
Robert Harvey

ใช่จริง ๆ แล้วนิยาม "ชุด" ดูเหมือนจะคล้ายกันมากในภาษาส่วนใหญ่: คอนเทนเนอร์ที่ยอมรับเฉพาะองค์ประกอบที่ไม่ซ้ำกัน
Floella

6
"การเพิ่มแต่ละองค์ประกอบลงในเวกเตอร์อื่นและตรวจสอบว่าองค์ประกอบนั้นมีอยู่แล้ว" - นี่เป็นเพียงการนำตัวคุณเอง ดังนั้นคุณจะถามว่าทำไมใช้คุณลักษณะในตัวเมื่อคุณสามารถเขียนด้วยตัวเอง?
JacquesB

คำตอบ:


8

คุณถามถึงฉากโดยเฉพาะ แต่ฉันคิดว่าคำถามของคุณเกี่ยวกับแนวคิดที่ใหญ่กว่า: นามธรรม คุณถูกต้องอย่างแน่นอนที่คุณสามารถใช้ Vector เพื่อทำสิ่งนี้ (ถ้าคุณใช้ Java ให้ใช้ ArrayList แทน) แต่ทำไมต้องหยุดที่นั่น คุณต้องการ Vector เพื่ออะไร คุณสามารถทำได้ทั้งหมดด้วยอาร์เรย์

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

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

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

ในช่วงต้นของอาชีพ / การศึกษาของคุณให้ความสำคัญกับการสร้างอัลกอริทึมเหล่านี้และทำความเข้าใจกับพวกเขาและเป็นสิ่งสำคัญที่จะทำเช่นนั้น แต่นั่นไม่ใช่สิ่งที่นักพัฒนามืออาชีพทำตามปกติ พวกเขาใช้วิธีการเหล่านี้เพื่อสร้างสิ่งที่น่าสนใจมากขึ้นและการใช้งานที่สร้างไว้ล่วงหน้าและเชื่อถือได้จะช่วยประหยัดเวลาของเรือ


23

ฉันคิดว่าขึ้นอยู่กับภาษาที่ใช้อาจมีประสิทธิภาพแตกต่างกัน แต่มีเหตุผลที่จะใช้ชุดอื่นนอกเหนือจากที่?

โอ้ใช่ (แต่มันไม่ได้ประสิทธิภาพ)

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

เขียนรหัสของคุณเองเพื่อทำเช่นนั้นและคุณต้องกังวลเกี่ยวกับมัน bleh ใครอยากจะทำอย่างนั้น?

โดยพื้นฐาน: อัลกอริทึมชนิดใดที่ต้องมีชุดและไม่ควรทำกับประเภทคอนเทนเนอร์อื่น ๆ ?

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

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

ป้อนคำอธิบายรูปภาพที่นี่

และ @germi ชี้ให้เห็นอย่างถูกต้องหากคุณใช้คอลเลกชันที่เหมาะสมสำหรับงานรหัสของคุณจะกลายเป็นเรื่องง่ายสำหรับผู้อื่นในการอ่าน


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

14

อย่างไรก็ตามฉันสามารถทำได้โดยการเพิ่มอิลิเมนต์แต่ละตัวในเวกเตอร์อื่นและตรวจสอบว่ามีอิลิเมนต์อยู่แล้ว

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

เหตุใดคุณจึงต้องการทำเช่นนั้นโดยใช้การติดตั้งที่มีอยู่ทดสอบและมีประสิทธิภาพ


6

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

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

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


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

1
@ gnasher729 ความรับผิดชอบของผู้ดำเนินการของชุดรวมถึงการตรวจสอบการมีอยู่ แต่ผู้ใช้ชุดสามารถfor 1..100: set.insert(10)และยังรู้ว่ามีเพียง 10 ในชุด
Caleth

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

4

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

คุณสามารถเลียนแบบ Set behavior (ไม่สนใจประสิทธิภาพ) ด้วย Array ได้หรือไม่? ใช่แล้ว! ทุกครั้งที่คุณแทรกคุณสามารถตรวจสอบว่าองค์ประกอบนั้นมีอยู่ในอาร์เรย์แล้วหรือไม่จากนั้นเพิ่มองค์ประกอบนั้นหากไม่พบ แต่นั่นคือสิ่งที่คุณต้องระวังและจำทุกครั้งที่คุณใส่เข้าไปในชุด Array-Psuedo โอ้สิ่งที่คุณแทรกโดยตรงโดยไม่ต้องตรวจสอบซ้ำกันก่อน? Welp อาร์เรย์ของคุณมีค่าคงที่ (ซึ่งองค์ประกอบทั้งหมดไม่ซ้ำกันและเท่าเทียมกันซึ่งไม่มีรายการซ้ำ)

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

ฉันจะตอบกลับพร้อมกับแถลงการณ์และติดตามคำถาม:

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

คุณสามารถใช้ตัวชี้แบบดิบและออฟเซ็ตคงที่เพื่อเลียนแบบอาเรย์หรือไม่? ใช่แล้ว! ทุกครั้งที่คุณใส่คุณสามารถตรวจสอบว่าออฟเซ็ตไม่ออกนอกตอนท้ายของหน่วยความจำที่จัดสรรไว้ที่คุณใช้งานอยู่ แต่นั่นคือสิ่งที่คุณต้องรู้ตัวและจำทุกครั้งที่คุณใส่เข้าไปใน Pseudo-Array โอ้คุณแทรกอะไรเข้าไปหนึ่งครั้งโดยตรงโดยไม่ตรวจสอบการชดเชยก่อน Welp มีความผิดพลาดในการแบ่งกลุ่มด้วยชื่อของคุณ!

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


3

มีอัลกอริธึมตามชุดทุกชนิดโดยเฉพาะอย่างยิ่งที่คุณต้องดำเนินการทางแยกและสหภาพชุดและมีผลเป็นชุด

อัลกอริธึมที่ตั้งค่าไว้ถูกใช้อย่างมากในอัลกอริธึมการค้นหาพา ธ ที่หลากหลายเป็นต้น

สำหรับไพรเมอร์เกี่ยวกับทฤษฎีเซตลองดูที่ลิงค์นี้: http://people.umass.edu/partee/NZ_2006/Set%20Theory%20Basics.pdf

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


1

จริง ๆ แล้วฉันพบว่าชุดภาชนะมาตรฐานส่วนใหญ่จะไร้ประโยชน์ตัวเองและชอบที่จะใช้อาร์เรย์ แต่ฉันทำมันในวิธีที่แตกต่างกัน

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

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

ที่นอกเหนือ ...

อย่างไรก็ตามฉันสามารถทำได้โดยการเพิ่มอิลิเมนต์แต่ละตัวในเวกเตอร์อื่นและตรวจสอบว่ามีอิลิเมนต์อยู่แล้ว

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

โดยพื้นฐาน: อัลกอริทึมชนิดใดที่ต้องมีชุดและไม่ควรทำกับประเภทคอนเทนเนอร์อื่น ๆ ?

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

ออกไปสัมผัส

เอาล่ะฉันได้รับคำถามที่ดีจาก JimmyJames เกี่ยวกับวิธีการตั้งสี่แยกนี้ มันค่อนข้างเบี่ยงเบนไปจากเรื่อง แต่ก็ดีฉันสนใจที่จะเห็นผู้คนจำนวนมากใช้วิธีการล่วงล้ำขั้นพื้นฐานนี้เพื่อกำหนดจุดตัดเพื่อที่พวกเขาจะไม่สร้างโครงสร้างเสริมทั้งหมดเช่นต้นไม้ไบนารีที่สมดุลและตารางแฮชเพียงเพื่อวัตถุประสงค์ในการปฏิบัติการ ดังที่ได้กล่าวถึงความต้องการขั้นพื้นฐานคือรายการองค์ประกอบการคัดลอกตื้น ๆ เพื่อให้พวกเขากำลังจัดทำดัชนีหรือชี้ไปที่องค์ประกอบที่ใช้ร่วมกันซึ่งสามารถ "ทำเครื่องหมาย" ตามการส่งผ่านโดยผ่านรายการแรกหรืออาร์เรย์ที่ไม่เรียงลำดับหรืออะไรก็ตาม ผ่านรายการที่สอง

อย่างไรก็ตามสิ่งนี้สามารถทำได้จริงในบริบทมัลติเธรดโดยไม่ต้องสัมผัสองค์ประกอบโดยที่:

  1. มวลรวมทั้งสองมีดัชนีองค์ประกอบ
  2. ช่วงของดัชนีไม่ใหญ่เกินไป (พูดว่า [0, 2 ^ 26), ไม่นับพันล้านหรือมากกว่า) และมีความหนาแน่นสูงพอสมควร

สิ่งนี้ทำให้เราสามารถใช้อาเรย์แบบขนาน (เพียงหนึ่งบิตต่อองค์ประกอบ) เพื่อวัตถุประสงค์ในการตั้งค่า แผนภาพ:

ป้อนคำอธิบายรูปภาพที่นี่

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

หากดัชนีไม่ได้ถูกครอบครองอย่างหนาแน่นและกระจัดกระจายอยู่นี่ก็ยังคงสามารถนำไปใช้กับการดำเนินการที่เหมาะสมของอาร์เรย์บิตบิต / บูลีนแบบเบาบางเช่นเดียวกับที่เก็บหน่วยความจำในชิ้น 512 บิต (64 ไบต์ต่อโหนด ) และข้ามการจัดสรรบล็อคต่อเนื่องที่ว่างเปล่าอย่างสมบูรณ์ โอกาสที่คุณกำลังใช้สิ่งนี้อยู่แล้วหากโครงสร้างข้อมูลส่วนกลางของคุณถูกครอบครองโดยองค์ประกอบอย่างกระจัดกระจาย

ป้อนคำอธิบายรูปภาพที่นี่

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

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

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

// 'func' receives a range of indices to
// process.
set_intersection(func):
{
    parallel_bits = bit_pool.acquire()

    // Mark the indices found in the first list.
    for each index in list1:
        parallel_bits[index] = 1

    // Look for the first element in the second list 
    // that intersects.
    first = -1
    for each index in list2:
    {
         if parallel_bits[index] == 1:
         {
              first = index
              break
         }
    }

    // Look for elements that don't intersect in the second
    // list to call func for each range of elements that do
    // intersect.
    for each index in list2 starting from first:
    {
        if parallel_bits[index] != 1:
        {
             func(first, index)
             first = index
        }
    }
    If first != list2.num-1:
        func(first, list2.num)
}

... หรือบางสิ่งบางอย่างในลักษณะนี้ ส่วนที่แพงที่สุดของ pseudocode ในไดอะแกรมแรกนั้นอยู่intersection.append(index)ในลูปที่สองและจะใช้std::vectorกับขนาดของรายการขนาดเล็กล่วงหน้าด้วย

จะทำอย่างไรถ้าฉันทำสำเนาลึกลงไป

หยุดนั่นสิ! หากคุณต้องการตั้งค่าทางแยกมันก็หมายความว่าคุณกำลังทำซ้ำข้อมูลเพื่อตัดกัน โอกาสที่แม้กระทั่งวัตถุที่เล็กที่สุดของคุณก็ไม่เล็กกว่าดัชนี 32 บิต เป็นไปได้มากที่จะลดช่วงการกำหนดตำแหน่งขององค์ประกอบของคุณเป็น 2 ^ 32 (2 ^ 32 องค์ประกอบไม่ใช่ 2 ^ 32 ไบต์) เว้นแต่ว่าคุณต้องการองค์ประกอบมากกว่า 4.3 พันล้านรายการทันทีที่ต้องการโซลูชันที่แตกต่างกันโดยสิ้นเชิง ( และแน่นอนว่าไม่ได้ใช้ชุดคอนเทนเนอร์ในหน่วยความจำ)

คีย์ตรงกัน

ในกรณีที่เราต้องทำการตั้งค่าการทำงานที่องค์ประกอบไม่เหมือนกัน แต่อาจมีคีย์ที่ตรงกันได้ ในกรณีนั้นความคิดเดียวกันข้างต้น เราเพียงต้องการแมปรหัสที่ไม่ซ้ำกันกับดัชนี หากคีย์เป็นสตริงตัวอย่างเช่นสตริง interned สามารถทำได้ ในกรณีเหล่านี้โครงสร้างข้อมูลที่ดีเช่น trie หรือตารางแฮชถูกเรียกเพื่อแม็พคีย์สตริงกับดัชนี 32- บิต แต่เราไม่ต้องการโครงสร้างดังกล่าวเพื่อทำการตั้งค่าบนดัชนี 32- บิตที่เกิดขึ้น

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

ฉันรักดัชนี!

ฉันรักดัชนีเช่นเดียวกับพิซซ่าและเบียร์ เมื่อฉันอายุ 20 ปีฉันได้เป็น C ++ และเริ่มออกแบบโครงสร้างข้อมูลที่สอดคล้องกับมาตรฐานทุกชนิด (รวมถึงเทคนิคที่เกี่ยวข้องกับการลดความซับซ้อนของ ctor จากช่วง ctor ในเวลารวบรวม) เมื่อมองย้อนกลับไปนั้นเป็นการเสียเวลามาก

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

ตัวอย่างบางกรณีใช้กรณีที่คุณสามารถสันนิษฐานได้ว่าค่าเฉพาะใด ๆ ของTมีดัชนีที่ไม่ซ้ำกันและจะมีอินสแตนซ์ที่อยู่ในอาร์เรย์ส่วนกลาง:

ประเภท Radix มัลติเธรดซึ่งทำงานได้ดีกับจำนวนเต็มไม่ได้ลงนามดัชนี จริง ๆ แล้วฉันมีการจัดเรียง radix แบบมัลติเธรดซึ่งใช้เวลาประมาณ 1 ใน 10 ของเวลาในการจัดเรียงองค์ประกอบต่างๆนับร้อยล้านรายการในแบบเรียงลำดับขนานของ Intel และ Intel นั้นเร็วกว่าstd::sortการป้อนข้อมูลขนาดใหญ่ถึง4 เท่า แน่นอนว่า Intel นั้นมีความยืดหยุ่นมากกว่าเนื่องจากเป็นแบบเรียงตามการเปรียบเทียบและสามารถเรียงลำดับตามพจนานุกรมได้ดังนั้นจึงเป็นการเปรียบเทียบแอปเปิ้ลกับส้ม แต่ที่นี่ฉันมักจะต้องการส้มเท่านั้นเช่นฉันอาจใช้ Radix sort Pass เพียงเพื่อให้ได้รูปแบบการเข้าถึงหน่วยความจำที่เป็นมิตรกับแคชหรือกรองการทำซ้ำอย่างรวดเร็ว

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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

แน่นอนว่าอาร์เรย์แบบขนานจะถูกดึงออกมาเนื่องจากลักษณะข้อผิดพลาดที่เกิดขึ้นได้ง่ายในการรักษาอาร์เรย์แบบขนานในการซิงค์ซึ่งกันและกัน เมื่อใดก็ตามที่เราลบองค์ประกอบที่ดัชนี 7 ออกจากอาร์เรย์ "root" เราก็ต้องทำสิ่งเดียวกันสำหรับ "children" อย่างไรก็ตามมันง่ายพอที่ในภาษาส่วนใหญ่ที่จะทำให้แนวความคิดนี้เป็นที่เก็บวัตถุประสงค์ทั่วไปเพื่อให้ตรรกะที่ยุ่งยากในการรักษาอาร์เรย์ขนานในการซิงค์กับแต่ละอื่น ๆ จะต้องมีอยู่ในที่เดียวตลอดทั้ง codebase ทั้งหมดและคอนเทนเนอร์อาร์เรย์แบบขนานสามารถ ใช้การดำเนินการของ sparse array ด้านบนเพื่อหลีกเลี่ยงการสูญเสียความทรงจำมากมายสำหรับพื้นที่ว่างที่ต่อเนื่องกันในอาร์เรย์ที่จะเรียกคืนเมื่อมีการแทรกครั้งต่อไป

เพิ่มเติมอย่างประณีต: Spits Bitset Tree

เอาล่ะฉันได้รับการร้องขอให้ทำอย่างละเอียดมากกว่านี้ซึ่งฉันคิดว่ามันเหน็บแนม แต่ฉันจะทำอย่างนั้นต่อไปเพราะมันสนุกมาก! หากผู้คนต้องการที่จะนำความคิดนี้ไปสู่ระดับใหม่ทั้งหมดก็เป็นไปได้ที่จะดำเนินการทางแยกชุดโดยไม่ต้องวนซ้ำเชิงเส้นผ่านองค์ประกอบ N + M นี่คือโครงสร้างข้อมูลที่ดีที่สุดของฉันที่ฉันใช้มานานและโดยทั่วไปแล้วset<int>:

ป้อนคำอธิบายรูปภาพที่นี่

เหตุผลที่สามารถดำเนินการแยกชุดโดยไม่ต้องตรวจสอบแต่ละองค์ประกอบในรายการทั้งสองเป็นเพราะบิตชุดเดียวที่รากของลำดับชั้นสามารถระบุได้ว่าพูดล้านองค์ประกอบที่อยู่ติดกันในชุด เพียงแค่ตรวจสอบหนึ่งบิตเราสามารถรู้ได้ว่าดัชนี N ในช่วงนั้น[first,first+N)อยู่ในเซตซึ่ง N อาจมีจำนวนมาก

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


2
"ในการคำนวณการตั้งค่าทางแยกฉันวนซ้ำแถวแรกและทำเครื่องหมายองค์ประกอบด้วยบิตเดียวจากนั้นฉันวนซ้ำแถวลำดับที่สองและมองหาองค์ประกอบที่ทำเครื่องหมาย" คุณทำเครื่องหมายไว้ที่ไหนในอาเรย์ที่สอง
JimmyJames

1
โอ้ฉันเข้าใจแล้วว่าคุณกำลัง 'เก็บข้อมูล' วัตถุเดียวที่เป็นตัวแทนของแต่ละค่า มันเป็นเทคนิคที่น่าสนใจสำหรับชุดย่อยของกรณีการใช้งานสำหรับชุด ฉันไม่เห็นเหตุผลใด ๆ ที่จะไม่ใช้วิธีการนี้เป็นการดำเนินการในคลาสชุดของคุณเอง
JimmyJames

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

1
ดูเหมือนว่าสิ่งนี้จะมีประโยชน์ในโซลูชันฐานข้อมูล แต่ฉันไม่รู้ว่ามีการนำไปใช้ในวิธีนี้หรือไม่ ขอบคุณที่ฝากไว้ที่นี่ คุณทำให้ใจของฉันทำงาน
JimmyJames

1
คุณช่วยอธิบายเพิ่มเติมอีกหน่อยได้ไหม? ;) ฉันจะตรวจสอบเมื่อฉันมีเวลา (มาก)
JimmyJames

-1

ในการตรวจสอบว่าชุดที่มีองค์ประกอบ n ประกอบด้วยอีกองค์ประกอบหนึ่ง X มักจะใช้เวลาคงที่ ในการตรวจสอบว่าอาร์เรย์ที่มีองค์ประกอบ n ประกอบด้วยองค์ประกอบอื่น X ใช้เวลาปกติ O (n) ไม่ดี แต่ถ้าคุณต้องการลบรายการที่ซ้ำออกจากรายการ n ก็จะใช้เวลา O (n) ในเวลาแทน O (n ^ 2); 100,000 รายการจะนำคอมพิวเตอร์ของคุณไปที่หัวเข่า

และคุณกำลังขอเหตุผลเพิ่มเติมหรือไม่ "นอกเหนือจากการถ่ายทำคุณสนุกกับการถ่ายทำตอนเย็นคุณลินคอล์นไหม"


2
ฉันคิดว่าคุณอาจต้องการอ่านอีกครั้ง การใช้เวลา O (n) แทนเวลาO (n²) ถือเป็นเรื่องดี
JimmyJames

บางทีคุณอาจยืนบนหัวของคุณในขณะที่อ่านสิ่งนี้? OP ถามว่า "ทำไมไม่ลองใช้อาร์เรย์"
gnasher729

2
ทำไมการเปลี่ยนจาก O (n²) ถึง O (n) จะนำ 'คอมพิวเตอร์มาที่หัวเข่า' ฉันต้องพลาดมันในชั้นเรียนของฉัน
JimmyJames
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.