ฉันควรใช้ HashSet <T> type เมื่อใด


136

ฉันกำลังสำรวจHashSet<T>ประเภท แต่ฉันไม่เข้าใจว่ามันอยู่ที่ใดในคอลเล็กชัน

สามารถใช้แทน a List<T>? ฉันจินตนาการว่าประสิทธิภาพของ a HashSet<T>จะดีขึ้น แต่ฉันไม่เห็นการเข้าถึงองค์ประกอบของแต่ละบุคคล

เป็นเพียงการแจงนับเท่านั้น?

คำตอบ:


229

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

การถามว่าคุณสามารถดึงองค์ประกอบเดียวได้หรือไม่ (เช่นset[45]) เป็นการเข้าใจแนวคิดของชุดนั้นผิด ไม่มีสิ่งที่เรียกว่าองค์ประกอบที่ 45 ของเซต สินค้าในชุดไม่มีการสั่งซื้อ ชุด {1, 2, 3} และ {2, 3, 1} เหมือนกันทุกประการเนื่องจากมีสมาชิกภาพเหมือนกันและการเป็นสมาชิกก็สำคัญ

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

ชุดมีจำนวน จำกัด และมีสมาชิกที่ไม่ซ้ำใคร ในทางกลับกันมันเร็วมาก


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

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

111

นี่คือตัวอย่างที่แท้จริงของสถานที่ที่ฉันใช้ a HashSet<string>:

ส่วนหนึ่งของการเน้นไวยากรณ์ของฉันสำหรับไฟล์ UnrealScript เป็นคุณลักษณะใหม่ที่ความคิดเห็นไฮไลท์ Doxygen สไตล์ ฉันจำเป็นต้องสามารถบอกได้ว่า a @หรือ\คำสั่งนั้นถูกต้องเพื่อพิจารณาว่าจะแสดงเป็นสีเทา (ถูกต้อง) หรือสีแดง (ไม่ถูกต้อง) ฉันมีHashSet<string>คำสั่งที่ถูกต้องทั้งหมดดังนั้นเมื่อใดก็ตามที่ฉันกด@xxxโทเค็นในตัวเล็กซ์ฉันจะใช้validCommands.Contains(tokenText)เป็นการตรวจสอบความถูกต้อง O (1) ของฉัน ฉันไม่สนใจอะไรเลยนอกจากการมีอยู่ของคำสั่งในชุดคำสั่งที่ถูกต้อง ลองดูทางเลือกอื่นที่ฉันเผชิญ:

  • Dictionary<string, ?>: ประเภทใดที่ฉันใช้สำหรับค่า? ContainsKeyค่าที่มีความหมายตั้งแต่ฉันแค่ไปกับการใช้งาน หมายเหตุ: ก่อนหน้า. NET 3.0 นี่เป็นทางเลือกเดียวสำหรับการค้นหา O (1) - HashSet<T>ถูกเพิ่มสำหรับ 3.0 และขยายเพื่อใช้งานISet<T>สำหรับ 4.0
  • List<string>: ถ้าฉันจัดเรียงรายการไว้ฉันสามารถใช้ได้BinarySearchซึ่งก็คือ O (log n) (ไม่เห็นข้อเท็จจริงนี้ที่กล่าวถึงข้างต้น) อย่างไรก็ตามเนื่องจากรายการคำสั่งที่ถูกต้องของฉันเป็นรายการคงที่ที่ไม่เคยเปลี่ยนแปลงสิ่งนี้จะไม่เหมาะสมไปกว่า ...
  • string[]: อีกครั้งArray.BinarySearchให้ประสิทธิภาพ O (log n) หากรายการสั้นนี่อาจเป็นตัวเลือกที่มีประสิทธิภาพดีที่สุด มันก็จะมีค่าใช้จ่ายน้อยกว่าพื้นที่HashSet, หรือDictionary Listถึงแม้BinarySearchจะไม่เร็วกว่าสำหรับชุดใหญ่ แต่สำหรับชุดเล็กก็ควรค่าแก่การทดลอง ของฉันมีหลายร้อยรายการดังนั้นฉันจึงส่งต่อไป

24

HashSet<T>ดำเนินการICollection<T>อินเตอร์เฟซ:

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

การList<T>ดำเนินการIList<T>ซึ่งขยายไฟล์ICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

HashSet ได้กำหนดความหมายโดยใช้แฮชแท็กภายใน:

ชุดคือคอลเล็กชันที่ไม่มีองค์ประกอบที่ซ้ำกันและองค์ประกอบที่ไม่เรียงลำดับกัน

HashSet ได้รับอะไรบ้างหากสูญเสียพฤติกรรมดัชนี / ตำแหน่ง / รายการ

การเพิ่มและการดึงรายการจาก HashSet จะเกิดขึ้นโดยตัวออบเจ็กต์เองเสมอไม่ใช่ผ่านตัวสร้างดัชนีและใกล้เคียงกับการดำเนินการ O (1) (รายการคือ O (1) เพิ่ม, O (1) ดึงโดยดัชนี, O (n) ค้นหา / ลบ)

พฤติกรรมของ HashSet สามารถเปรียบเทียบได้กับการใช้ a Dictionary<TKey,TValue>โดยเพิ่ม / ลบคีย์เป็นค่าเท่านั้นและละเว้นค่าพจนานุกรมเอง คุณคาดว่าคีย์ในพจนานุกรมจะไม่มีค่าซ้ำกันและนั่นคือจุดสำคัญของส่วน "Set"


14

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


21
Performance would be a bad reason to choose HashSet over List: ฉันไม่เห็นด้วยกับคุณ นั่นเป็นการบอกว่าการเลือก Dictionray แทนสองรายการไม่ได้ช่วยในการแสดง ดูบทความต่อไปนี้
Oscar Mederos

11
@ ออสการ์: ฉันไม่ได้บอกว่าฉากนั้นไม่เร็วกว่านี้ - ฉันบอกว่านั่นจะเป็นพื้นฐานที่ไม่ดีในการเลือก หากคุณกำลังพยายามที่จะเป็นตัวแทนของคอลเลกชั่นที่สั่งซื้อชุดก็จะใช้ไม่ได้และจะเป็นความผิดพลาดที่จะพยายามใส่ที่ใส่รองเท้าเข้าไป หากคอลเลกชันที่คุณต้องการไม่มีคำสั่งซื้อชุดก็สมบูรณ์แบบและรวดเร็ว แต่สิ่งที่สำคัญคือคำถามแรก: คุณพยายามจะเป็นตัวแทนอะไร?
Carl Manaster

2
แต่ลองคิดดูสิ หากคุณต้องการตรวจสอบอยู่เสมอว่าสตริงที่กำหนดเป็นสมาชิกของคอลเลคชัน 10,000 สตริงในทางเทคนิคstring[].ContainsและHashSet<string>.Containsแสดงเจตนาของคุณได้ดีเท่า ๆ กัน เหตุผลในการเลือก HashSet ก็คือมันจะทำงานได้เร็วขึ้นมาก
Casey

12

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

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


แต่ Set ไม่อนุญาตให้ดึงองค์ประกอบเดี่ยว? ชอบชุด [45]?
Joan Venge

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

11

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

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

หลังจากรันโค้ดเหล่านั้นแล้ว uniqueStrings จะถือ {"abc", "ghjr", "yre", "obm", "qwrt", "vyeu"};


6

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

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


หากคุณทำซ้ำเพียงอย่างเดียววิธีการ HashSet จะเพิ่มการใช้หน่วยความจำเล็กน้อยเมื่อเทียบกับรายการ
SamuelWarren

5

HashSet<T>เป็นโครงสร้างข้อมูลในกรอบ. NET ที่สามารถแสดงชุดทางคณิตศาสตร์เป็นวัตถุ ในกรณีนี้จะใช้รหัสแฮช ( GetHashCodeผลลัพธ์ของแต่ละรายการ) เพื่อเปรียบเทียบความเท่าเทียมกันขององค์ประกอบชุด

ชุดแตกต่างจากรายการที่อนุญาตให้เกิดองค์ประกอบเดียวกันที่มีอยู่ภายในได้เพียงรายการเดียว HashSet<T>จะกลับมาfalseถ้าคุณพยายามเพิ่มองค์ประกอบที่สองที่เหมือนกัน อันที่จริงการค้นหาองค์ประกอบนั้นรวดเร็วมาก ( O(1)เวลา) เนื่องจากโครงสร้างข้อมูลภายในเป็นเพียงแฮชแท็ก

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


1
การเพิ่มรายการที่มีอยู่ลงในชุดจะไม่ทำให้เกิดข้อยกเว้น เพิ่มจะส่งคืนเท็จ นอกจากนี้: การค้นหาแฮชในทางเทคนิคคือ O (n) ไม่ใช่ O (1) เว้นแต่คุณจะมีฟังก์ชันแฮชที่สมบูรณ์แบบ แน่นอนในทางปฏิบัติคุณจะหนีไปโดยสมมติว่าเป็น O (1) เว้นแต่ว่าฟังก์ชันการแฮชจะไม่ดีจริงๆ
sepp2k

1
@ sepp2k: ใช่มันจึงส่งคืนบูลีน ... ประเด็นคือมันแจ้งให้คุณทราบ และการค้นหาแฮชเป็นกรณีที่เลวร้ายที่สุด O (n) ถ้าคุณกำลังเก็บข้อมูลแย่มาก - โดยทั่วไปจะใกล้เคียงกับ O (1) มาก
Noldorin

4

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

เป็นชื่อที่แสดงถึงHashedSet<T>เป็นโครงสร้างข้อมูลที่ดำเนินการกำหนดความหมาย โครงสร้างข้อมูลได้รับการปรับให้เหมาะสมเพื่อใช้การดำเนินการชุด (เช่น Union, Difference, Intersect) ซึ่งไม่สามารถทำได้อย่างมีประสิทธิภาพกับการใช้งาน List แบบเดิม

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


2
ข้อแม้อื่น: โดยทั่วไปแล้วชุดจะอนุญาตให้เกิดองค์ประกอบได้เพียงครั้งเดียว
Steve Guidi

2

ในสถานการณ์จำลองพื้นฐานHashSet<T>ควรใช้เมื่อคุณต้องการการดำเนินการชุดที่เฉพาะเจาะจงมากขึ้นในสองคอลเลกชันมากกว่าที่ LINQ ให้ไว้ วิธีการ LINQ ชอบDistinct, Union, IntersectและExceptมีความเพียงพอในสถานการณ์ส่วนใหญ่ แต่บางครั้งคุณอาจจะต้องดำเนินงานเม็ดเล็กมากขึ้นและHashSet<T>ให้:

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

ความแตกต่างอีกอย่างระหว่างวิธีการ LINQ และHashSet<T>"การทับซ้อนกัน" คือ LINQ จะส่งคืนใหม่เสมอIEnumerable<T>และHashSet<T>วิธีการแก้ไขการรวบรวมแหล่งที่มา


1

ในระยะสั้น - เมื่อใดก็ตามที่คุณถูกล่อลวงให้ใช้พจนานุกรม (หรือพจนานุกรมโดยที่ S เป็นสมบัติของ T) คุณควรพิจารณา HashSet (หรือ HashSet + ที่ใช้ IEquatable บน T ซึ่งเท่ากับ S)


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