“ ชุด” ควรมีวิธีรับไหม


22

มามีคลาส C # นี้ (มันจะเหมือนกันใน Java)

public class MyClass {
   public string A {get; set;}
   public string B {get; set;}

   public override bool Equals(object obj) {
        var item = obj as MyClass;

        if (item == null || this.A == null || item.A == null)
        {
            return false;
        }
        return this.A.equals(item.A);
   }

   public override int GetHashCode() {
        return A != null ? A.GetHashCode() : 0;
   }
}

อย่างที่คุณเห็นความเสมอภาคของสองอินสแตนซ์นั้นMyClassขึ้นอยู่กับAเท่านั้น ดังนั้นอาจมีสองอินสแตนซ์ที่เท่ากัน แต่เก็บข้อมูลที่แตกต่างกันในBทรัพย์สินของพวกเขา

ในไลบรารีคอลเลกชันมาตรฐานของหลายภาษา (รวมถึง C # และ Java ของหลักสูตร) ​​มีSet( HashSetใน C #) ซึ่งเป็นคอลเลกชันซึ่งสามารถเก็บได้มากที่สุดหนึ่งรายการจากแต่ละชุดของอินสแตนซ์ที่เท่ากัน

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

HashSet<MyClass> mset = new HashSet<MyClass>();
mset.Add(new MyClass {A = "Hello", B = "Bye"});

//I can do this
if (mset.Contains(new MyClass {A = "Hello", B = "See you"})) {
    //something
}

//But I cannot do this, because Get does not exist!!!
MyClass item = mset.Get(new MyClass {A = "Hello", B = "See you"});
Console.WriteLine(item.B); //should print Bye

วิธีเดียวที่จะดึงไอเท็มของฉันคือการวนซ้ำในคอลเล็กชั่นทั้งหมดและตรวจสอบไอเท็มทั้งหมดเพื่อความเท่าเทียมกัน อย่างไรก็ตามต้องใช้O(n)เวลามากกว่าO(1)!

ฉันไม่พบภาษาใด ๆ ที่รองรับการรับจากชุดจนถึง ภาษา "ทั่วไป" ทั้งหมดที่ฉันรู้จัก (Java, C #, Python, Scala, Haskell ... ) ดูเหมือนได้รับการออกแบบในลักษณะเดียวกัน: คุณสามารถเพิ่มรายการได้ แต่คุณไม่สามารถเรียกคืนได้ มีเหตุผลที่ดีหรือไม่ที่ทำไมภาษาทั้งหมดเหล่านี้ไม่สนับสนุนสิ่งที่ง่ายและมีประโยชน์ชัดเจน? พวกเขาไม่สามารถผิดทั้งหมดใช่มั้ย มีภาษาที่รองรับหรือไม่ อาจจะเรียกคืนบางรายการจากชุดที่ไม่ถูกต้อง แต่ทำไม?


มีคำถาม SO ที่เกี่ยวข้องสองสามข้อ:

/programming/7283338/getting-an-element-from-a-set

/programming/7760364/how-to-retrieve-actual-item-from-hashsett


12
C ++ std::setรองรับการดึงข้อมูลวัตถุดังนั้นภาษา "ทั่วไป" ไม่เหมือนทุกภาษาที่คุณอธิบาย
Reinstate Monica

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

7
เรื่องจริง: ใน Java Set<E>การใช้งานจำนวนมากอยู่Map<E,Boolean>ภายใน
corsiKa

10
พูดกับคน A : "สวัสดีคุณช่วยพาคน A มาที่นี่ได้ไหม"
Brad Thomas

7
แบ่งนี้ reflexivity ( a == bจริงเสมอ) this.A == nullในกรณีที่ การif (item == null || this.A == null || item.A == null)ทดสอบคือ "เกินกำหนด" และตรวจสอบไปมากอาจเป็นไปได้ที่จะสร้างรหัส "คุณภาพสูง" ปลอม ฉันเห็น "การตรวจสอบมากเกินไป" และแก้ไขให้ถูกต้องตลอดเวลาในการตรวจสอบรหัส
usr

คำตอบ:


66

ปัญหาที่นี่ไม่ได้HashSetขาดGetวิธีการมันเป็นที่รหัสของคุณไม่เข้าใจจากมุมมองของHashSetประเภท

นั่นGetเป็นวิธีการที่มีประสิทธิภาพ "ได้รับฉันค่านี้โปรด" ซึ่งชาวบ้านกรอบ NET เป็นตุเป็นตะจะตอบ "ใช่มั้ย? คุณมีค่าที่<confused face />"

หากคุณต้องการจัดเก็บรายการแล้วดึงข้อมูลเหล่านั้นโดยยึดตามค่าที่แตกต่างกันเล็กน้อยจากนั้นใช้Dictionary<String, MyClass>ตามที่คุณสามารถทำได้:

var mset = new Dictionary<String, MyClass>();
mset.Add("Hello", new MyClass {A = "Hello", B = "Bye"});

var item = mset["Hello"];
Console.WriteLine(item.B); // will print Bye

ข้อมูลการรั่วไหลของความเท่าเทียมกันจากคลาสที่ห่อหุ้ม หากฉันต้องการเปลี่ยนชุดคุณสมบัติที่เกี่ยวข้องEqualsฉันจะต้องเปลี่ยนรหัสภายนอกMyClass...

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

HashSet<MyClass> mset = new HashSet<MyClass>();
mset.Add(new MyClass {A = "Hello", B = "Bye"});

if (mset.Contains(new MyClass {A = "Hello", B = "See you"})) 
{
    // this code is unreachable.
}

เพื่อป้องกันสิ่งนี้MyClassจำเป็นต้องมีการบันทึกไว้อย่างชัดเจนว่าเป็นรูปแบบที่เท่าเทียมกัน เมื่อทำอย่างนั้นแล้วมันจะไม่ถูกห่อหุ้มอีกต่อไปและเปลี่ยนวิธีการทำงานของความเสมอภาคนั้นจะทำลายหลักการเปิด / ปิด ดังนั้นมันไม่ควรเปลี่ยนแปลงดังนั้นจึงDictionary<String, MyClass>เป็นทางออกที่ดีสำหรับความต้องการที่แปลกนี้


2
@vojta, ในกรณีที่ใช้เป็นมันก็จะเรียกค่าตามที่สำคัญที่ใช้Dictionary<MyClass, MyClass> MyClass.Equals
David Arno

8
ฉันจะใช้อุปกรณ์Dictionary<MyClass, MyClass>ที่มาพร้อมกับที่เหมาะสมIEqualityComparer<MyClass>และดึงความสัมพันธ์ที่เท่ากันออกมาจากMyClassเหตุใดจึงMyClassต้องทราบเกี่ยวกับความสัมพันธ์นี้กับอินสแตนซ์ของมัน
Caleth

16
@vojta และความคิดเห็นที่นั่น: " meh การเอาชนะการดำเนินการเท่ากับเพื่อให้วัตถุที่ไม่เท่ากันคือ" เท่ากับ "เป็นปัญหาที่นี่ขอวิธีการที่ระบุว่า" รับวัตถุที่เหมือนกันกับวัตถุนี้ "แล้ว คาดหวังว่าวัตถุที่ไม่เหมือนกันที่จะถูกส่งคืนดูเหมือนจะบ้าและง่ายที่จะทำให้เกิดปัญหาการบำรุงรักษา "เป็นจุดบน นั่นคือมักจะมีปัญหากับ SO: ตอบข้อบกพร่องอย่างจริงจังได้รับ upvoted โดยชาวบ้านที่ยังไม่ได้คิดผ่าน implicants ของความปรารถนาของพวกเขาสำหรับการแก้ไขอย่างรวดเร็วรหัสเสียของพวกเขา ...
เดวิดอาร์โน

6
@DavidArno: เป็นสิ่งที่หลีกเลี่ยงไม่ได้แม้ว่าตราบใดที่เรายังคงใช้ภาษาที่แยกความแตกต่างระหว่างความเท่าเทียมและอัตลักษณ์ ;-) ถ้าคุณต้องการวัตถุ canonicalise ที่เท่ากัน แต่ไม่เหมือนกันคุณต้องใช้วิธีที่บอกว่าไม่ "เอาฉันเหมือนกัน วัตถุของวัตถุนี้ "แต่" ให้ฉันเป็นวัตถุมาตรฐานซึ่งเท่ากับวัตถุนี้ " ใครก็ตามที่คิดว่า HashSet.Get ในภาษาเหล่านี้จำเป็นต้องมีความหมายว่า "เอาวัตถุที่เหมือนกัน" มาให้ฉันผิดพลาดแล้ว
Steve Jessop

4
...reasonable to assume...คำตอบนี้มีงบผ้าห่มหลายอย่างเช่น ทั้งหมดนี้อาจเป็นจริงใน 99% ของกรณี แต่ยังคงความสามารถในการดึงรายการจากชุดสามารถเป็นประโยชน์ รหัสโลกแห่งความเป็นจริงไม่สามารถเป็นไปตามหลักการ POLA ฯลฯ ตัวอย่างเช่นหากคุณกำลังทำซ้ำสตริงตัวพิมพ์เล็ก - ใหญ่คุณอาจต้องการรับไอเท็ม "มาสเตอร์" Dictionary<string, string>เป็นวิธีแก้ปัญหา แต่ก็คุ้มราคา
usr

24

คุณมีรายการที่เป็น "ใน" ชุด - คุณผ่านมันเป็นกุญแจสำคัญ

"แต่ไม่ใช่อินสแตนซ์ที่ฉันเรียกว่าเพิ่มด้วย" - ใช่ แต่คุณอ้างว่ามันเท่ากัน

A Setเป็นกรณีพิเศษของMap| Dictionaryด้วย void เป็นประเภทค่า (ไม่ได้กำหนดวิธีการที่ไร้ประโยชน์ แต่ก็ไม่สำคัญ)

โครงสร้างข้อมูลที่คุณกำลังมองหาคือDictionary<X, MyClass>ที่Xใดก็ได้ที่ได้รับจากนอก MyClasses

ประเภทพจนานุกรม C # นั้นดีในเรื่องนี้เนื่องจากช่วยให้คุณสามารถจัดหา IEqualityComparer สำหรับคีย์ได้

สำหรับตัวอย่างที่กำหนดฉันจะมีดังต่อไปนี้:

public class MyClass {
   public string A {get; set;}
   public string B {get; set;}
}

public class MyClassEquivalentAs : IEqualityComparer<MyClass>{
   public override bool Equals(MyClass left, MyClass right) {
        if (Object.ReferenceEquals(left, null) && Object.ReferenceEquals(right, null))
        {
            return true;
        }
        else if (Object.ReferenceEquals(left, null) || Object.ReferenceEquals(right, null))
        {
            return false;
        }
        return left.A == right.A;
   }

   public override int GetHashCode(MyClass obj) {
        return obj?.A != null ? obj.A.GetHashCode() : 0;
   }
}

ใช้แล้ว:

var mset = new Dictionary<MyClass, MyClass>(new MyClassEquivalentAs());
var bye = new MyClass {A = "Hello", B = "Bye"};
var seeyou = new MyClass {A = "Hello", B = "See you"};
mset.Add(bye);

if (mset.Contains(seeyou)) {
    //something
}

MyClass item = mset[seeyou];
Console.WriteLine(item.B); // prints Bye

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

@supercat Dictionary<String, String>ในวันนี้ว่าจะประสบความสำเร็จกับ
MikeFHay

@MikeFHay: ใช่ แต่ดูเหมือนว่าจะไม่ค่อยเหมาะสมที่จะต้องเก็บแต่ละการอ้างอิงสตริงสองครั้ง
supercat

2
@supercat หากคุณหมายถึงสตริงที่เหมือนกันนั่นเป็นเพียงการฝึกงานสตริง ใช้สิ่งในตัว หากคุณหมายถึงการเป็นตัวแทน "บัญญัติ" บางประเภท (สิ่งที่ไม่สามารถทำได้โดยใช้เทคนิคการเปลี่ยนกรณีง่าย ๆ ฯลฯ ) ซึ่งฟังดูเหมือนว่าคุณต้องการดัชนี ฉันไม่เห็นปัญหาในการเก็บ "แบบฟอร์มที่ไม่ใช่บัญญัติ" เป็นคีย์ที่แมปกับรูปแบบบัญญัติ (ฉันคิดว่าสิ่งนี้ใช้ได้ดีพอ ๆ กันถ้าแบบฟอร์ม "มาตรฐาน" ไม่ใช่สตริง) หากนี่ไม่ใช่สิ่งที่คุณกำลังพูดถึงนั่นแสดงว่าคุณสูญเสียฉันไปโดยสิ้นเชิง
jpmc26

1
กำหนดเองComparerและDictionary<MyClass, MyClass>เป็นวิธีแก้ปัญหาอย่างจริงจัง ใน Java, เดียวกันสามารถทำได้โดยการTreeSetหรือบวกที่กำหนดเองTreeMap Comparator
Markus Kull

19

ปัญหาของคุณคือคุณมีแนวคิดที่ขัดแย้งกันสองประการเกี่ยวกับความเสมอภาค:

  • ความเท่าเทียมกันจริงที่ทุกสาขามีความเท่าเทียมกัน
  • ตั้งค่าความเท่าเทียมกันของสมาชิกโดยที่ A เท่ากัน

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

นอกจากนี้เรายังสามารถยืนยันได้ว่าชุดเป็นชนิดข้อมูลนามธรรมที่กำหนดโดยหมดจดโดยS contains xหรือx is-element-of Sความสัมพันธ์ ("ฟังก์ชั่นลักษณะ") หากคุณต้องการปฏิบัติการอื่น ๆ คุณไม่ได้กำลังมองหาชุด

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

ใน C # พจนานุกรมสามารถใช้ความสัมพันธ์ความเท่าเทียมกันอย่างชัดเจนฉันคิดว่า มิฉะนั้นความสัมพันธ์ดังกล่าวสามารถดำเนินการได้โดยการเขียนคลาส wrapper ด่วน pseudocode:

// The type you actually want to store
class MyClass { ... }

// A equivalence class of MyClass objects,
// with regards to a particular equivalence relation.
// This relation is implemented in EquivalenceClass.Equals()
class EquivalenceClass {
  public MyClass instance { get; }
  public override bool Equals(object o) { ... } // compare instance.A
  public override int GetHashCode() { ... } // hash instance.A
  public static EquivalenceClass of(MyClass o) { return new EquivalenceClass { instance = o }; }
}

// The set-like object mapping equivalence classes
// to a particular representing element.
class EquivalenceHashSet {
  private Dictionary<EquivalenceClass, MyClass> dict = ...;
  public void Add(MyClass o) { dict.Add(EquivalenceClass.of(o), o)}
  public bool Contains(MyClass o) { return dict.Contains(EquivalenceClass.of(o)); }
  public MyClass Get(MyClass o) { return dict.Get(EquivalenceClass.of(o)); }
}

"ดึงอินสแตนซ์เฉพาะจากชุด" ฉันคิดว่านี่จะสื่อความหมายของคุณโดยตรงถ้าคุณเปลี่ยน "อินสแตนซ์" เป็น "สมาชิก" เพียงข้อเสนอแนะเล็กน้อย =) +1
jpmc26

7

แต่ทำไมมันเป็นไปไม่ได้ที่จะได้รับรายการเฉพาะจากชุด?

เพราะนั่นไม่ใช่สิ่งที่ชุดสำหรับ

ขอผมใช้ตัวอย่างใหม่

"ฉันมี HashSet ที่ฉันต้องการเก็บวัตถุ MyClass ไว้และฉันต้องการที่จะรับมันโดยใช้คุณสมบัติ A ที่เท่ากับคุณสมบัติของวัตถุ A"

หากแทนที่ "HashSet" ด้วย "Collection", "objects" ด้วย "Values" และ "property A" ด้วย "Key" ประโยคจะกลายเป็น:

"ฉันมีคอลเล็กชันที่ฉันต้องการเก็บค่า MyClass ไว้และฉันต้องการที่จะรับมันโดยใช้คีย์ที่เท่ากับคีย์ของวัตถุ"

สิ่งที่ถูกอธิบายคือพจนานุกรม คำถามที่ถามจริงคือ "ทำไมฉันไม่สามารถใช้ HashSet เป็นพจนานุกรมได้"

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

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


6

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

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

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

ดังนั้น Setter on A จึงเป็นอันตราย

ตอนนี้คุณไม่มี B ที่ไม่เข้าร่วมในความเท่าเทียมกัน ปัญหาที่นี่ไม่ใช่ความหมายทางเทคนิค เนื่องจากการเปลี่ยนแปลงทางเทคนิค B นั้นเป็นกลางกับความเป็นจริงของความเท่าเทียมกัน Semantically B ต้องเป็นบางอย่างเช่นแฟล็ก "version"

ประเด็นก็คือ:

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

หาก B มีข้อมูลรุ่นคุณแสดงว่าสมมติฐานที่ก่อนหน้านี้เป็นรหัสที่มาจากรหัสเท่านั้น ตอนนี้คุณเห็นแล้วว่าวัตถุ Y เป็น X เวอร์ชั่นใหม่กว่า

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

เพื่อหลีกเลี่ยงการดำเนินการที่ซ่อนอยู่ "แทนที่เก่ากับวัตถุใหม่" ชุดไม่ควรมีวิธีการ หากคุณต้องการพฤติกรรมเช่นนี้คุณต้องทำให้ชัดเจนโดยการลบวัตถุเก่าและเพิ่มวัตถุใหม่

BTW: มันควรหมายความว่าอย่างไรถ้าคุณผ่านวัตถุที่เท่ากับวัตถุที่คุณต้องการได้ ที่ไม่สมเหตุสมผล รักษาความหมายของคุณให้สะอาดและอย่าทำเช่นนี้แม้ว่าจะไม่มีใครขัดขวางคุณในทางเทคนิค


7
"ทันทีที่คุณแทนที่เท่ากับคุณจะดีกว่าการแฮชโค้ดทันทีที่คุณทำเช่นนี้" อินสแตนซ์ "ของคุณไม่ควรเปลี่ยนสถานะภายในอีกครั้ง" ข้อความนั้นมีค่า +100 ตรงนั้น
David Arno

+1 สำหรับการชี้ให้เห็นถึงอันตรายของความเสมอภาคและแฮชโค้ดขึ้นอยู่กับสถานะที่ไม่แน่นอน
Hulk

3

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

ฉันยังไม่ได้เก็บไว้ถึงวันที่มีรายละเอียดการดำเนินการดังกล่าวดังนั้นฉันไม่สามารถบอกได้ว่าเหตุผลนี้ยังคงใช้ในเต็มรูปแบบใน Java ให้อยู่คนเดียวใน C # ฯลฯ แต่ถึงแม้ว่าHashSetถูก reimplemented ใช้หน่วยความจำน้อยกว่าHashMapในกรณีใด ๆ จะเป็นการเปลี่ยนแปลงที่ผิดพลาดเพื่อเพิ่มวิธีการใหม่ในSetอินเทอร์เฟซ ดังนั้นจึงเป็นเรื่องที่ค่อนข้างเจ็บปวดสำหรับผลประโยชน์ที่ทุกคนไม่เห็นว่ามีค่า


ทั้งใน Java มันอาจจะเป็นไปได้ที่จะให้default-implementation ที่จะทำเช่นนี้ในทางที่ไม่ทำลาย ดูเหมือนจะไม่เป็นการเปลี่ยนแปลงที่มีประโยชน์มากนัก
Hulk

@Hulk: ฉันอาจจะผิด แต่ฉันคิดว่าการใช้งานเริ่มต้นจะไม่มีประสิทธิภาพอย่างยิ่งเนื่องจากเป็นผู้ถามว่า "วิธีเดียวที่จะดึงรายการของฉันคือการทำซ้ำในการรวบรวมทั้งหมดและตรวจสอบรายการทั้งหมดเพื่อความเท่าเทียมกัน" จุดดีคุณสามารถทำในแบบย้อนหลังเข้ากันได้ แต่เพิ่ม gotcha ที่ฟังก์ชั่นรับผลเพียงรับประกันการทำงานในO(n)การเปรียบเทียบแม้ว่าฟังก์ชั่นแฮชจะให้การกระจายที่ดี จากนั้นการใช้งานของSetสิ่งนั้นจะแทนที่การใช้งานเริ่มต้นในอินเทอร์เฟซรวมถึงHashSetสามารถรับประกันได้ดีกว่า
Steve Jessop

ตกลง - ฉันไม่คิดว่ามันจะเป็นความคิดที่ดี จะมี precedences สำหรับชนิดของพฤติกรรมนี้แม้ว่า - List.get (ดัชนี int)หรือ - เพื่อรับการดำเนินการเริ่มต้นที่เพิ่งเพิ่มList.sort การรับประกันความซับซ้อนสูงสุดนั้นมาจากส่วนต่อประสาน แต่การใช้งานบางอย่างอาจทำได้ดีกว่าตัวอื่น ๆ
Hulk

2

มีภาษาหลักที่ชุดมีคุณสมบัติที่คุณต้องการ

ใน C ++ std::setเป็นชุดสั่งซื้อ มันมี.findวิธีการที่มองหาองค์ประกอบตามผู้ประกอบการสั่งซื้อ<หรือbool(T,T)ฟังก์ชั่นไบนารีที่คุณให้ คุณสามารถใช้ find เพื่อสร้างการดำเนินการ get ที่คุณต้องการ

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

สิ่งนี้จะช่วยให้มีประสิทธิภาพ:

std::set< std::string, my_string_compare > strings;
strings.find( 7 );

โดยที่my_string_compareเข้าใจวิธีการสั่งซื้อจำนวนเต็มและสตริงโดยไม่ต้องแปลงจำนวนเต็มเป็นสตริง (ในราคาที่อาจเกิดขึ้น)

สำหรับunordered_set(ชุดแฮชของ C ++) จะไม่มีแฟล็กโปร่งใสที่เทียบเท่า (ยัง) คุณจะต้องผ่าน a Tไปยังunordered_set<T>.findวิธีการ สามารถเพิ่มได้ แต่ต้องการแฮช==และ Hasher ซึ่งแตกต่างจากชุดที่สั่งซื้อซึ่งเพียงแค่ต้องการสั่งซื้อ

รูปแบบทั่วไปคือคอนเทนเนอร์จะทำการค้นหาจากนั้นให้ "iterator" แก่องค์ประกอบนั้นภายในคอนเทนเนอร์ ณ จุดนี้คุณจะได้รับองค์ประกอบภายในชุดหรือลบ ฯลฯ

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

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

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

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

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