Effiziente Weise zu Klonen, eine HashSet<T>?
Vor ein paar Tagen antwortete ich eine interessante Frage auf SO über HashSet<T>
. Eine mögliche Lösung beteiligt Klonen der hashset, und in meiner Antwort habe ich vorgeschlagen, so etwas zu tun:
HashSet<int> original = ...
HashSet<int> clone = new HashSet<int>(original);
Obwohl dieser Ansatz ist Recht einfach, ich vermute, es ist sehr ineffizient: der Konstruktor der neuen HashSet<T>
muss separat hinzufügen jedes Element aus der original-hashset, und überprüfen, wenn es nicht bereits vorhanden. Dies ist eindeutig eine Verschwendung von Zeit: da die source-Sammlung ist ein ISet<T>
es garantiert keine Duplikate enthalten. Es sollte eine Weise zu nutzen, dass wissen...
Idealerweise HashSet<T>
umsetzen sollte ICloneable
, aber leider ist es nicht der Fall. Ich habe auch überprüft, mit Reflektor, um zu sehen, wenn die HashSet<T>
Konstruktor hat etwas besonderes, wenn die source-Sammlung war ein hashset, aber es funktioniert nicht. Es konnte wahrscheinlich gemacht werden, mithilfe von reflektion auf private Felder, aber das wäre ein hässlicher hack,...
Also, hat jemand kommen mit eine clevere Lösung, die zum Klonen eines hashset effizienter ?
(Hinweis: diese Frage ist rein theoretisch, ich brauche das nicht zu tun, dass in einem realen Programm)
- hm, gute Frage, aber einfach nur neugierig, was sind die theoretischen Ineffizienzen sind wir besorgt? ich bin eingerostet auf meine Bestellung notation für abstrakte Daten-Typen, aber hätte es nicht auch ein check auf die Existenz innerhalb der Ziel-hash gesetzt werden, eine einfache O(1) - Kollision-test? ich bin damit einverstanden von einer Informations-Perspektive, könnte es "besser", aber können wir eine Schranke für es, und wäre es von Bedeutung?
- Ich vermute, Sie haben nicht ein HashSet<T>(ISet<T>) - Konstruktor ist, da jede Klasse umsetzen konnte ISet<T>, vielleicht schlecht; was bedeutet, dass die Anwesenheit von ISet<T> ist keine Garantie dafür, dass keine Duplikate vorhanden sind
- Ellinger, du hast wahrscheinlich Recht. Jedoch, Sie könnte ein HashSet<T>(HashSet<T>) - Konstruktor...
- Eigentlich ist das, was ich bin neugierig ist, warum Sie nicht ICloneable implementieren, ist es, weil eine Umsetzung wäre nicht mehr effizient dann der Konstruktor Sie endete Aufruf in Ihrem Sinne zu beantworten; also, warum die Mühe, wenn die Funktionalität bereits verfügbar. Das gleiche könnte möglicherweise sein, sagte für Ihre Kopie-Konstruktor. Natürlich scheint dies nicht plausibel angesichts Ihrer Bemerkung über 'und überprüfen Sie, wenn es nicht bereits vorhanden ist'. Hmmm.
- Auch die deserializer macht keine Annahmen und verwendet AddIfNotPresent(). Gute Idee, die Kultur verändert haben könnte. Dies ist ein no-go. Frage zum Klonen der ersten. Kostspielig sein sollte, gut, teuer. Große API-design.
- Würden Sie geschehen, zu wissen, der alle Strafen der Verwendung der Serialisierung im Allgemeinen? Getestet habe ich dies vergleichen mit dem Konstruktor vs serialisieren und der Konstruktor war fast 2x schneller im Durchschnitt, ohne überprüfung (3x mit Verifizierung) auf 10000 Punkt gesetzt. Bei größeren Mengen wird der Unterschied verringert sich mit Konstruktor noch schneller. Ich kann nach dem code, wenn Sie möchten.
- g, ich habe gerade einen kleinen test: Klon mit der spiegelung vs. Konstruktor-Aufruf. Auch mit dem Aufwand es bedeutet, die Reflexion ist in etwa doppelt so schnell. Also ich denke, ein echter Clone-Methode wäre viel schneller...
- Passant, gute Erläuterungen. In Bezug auf die Notwendigkeit der Klon ein hashset: wie gesagt, die Frage ist rein theoretisch, ich brauche nicht, es zu tun.
- M die Idee der Verwendung der Serialisierung in den Sinn gekommen, aber ich bedeutet, dass (1) die Objekte müssen serialisierbar sein sollen, und (2) jedes Element wird geklont... Meine Idee war es, eine flache Kopie des hashset, nicht eine Tiefe Kopie.
- Sie hat nicht ICloneable implementieren, weil es eine miese interface. blogs.msdn.com/b/brada/archive/2003/04/09/49935.aspx ist ein Grund.
- warum würden Sie denken, Sie haben nicht einige effiziente Art und Weise der Umsetzung der Klon über den Konstruktor? Wenn ich das schreiben von code, id-überprüfen Sie die Eingabe, und wenn ich wusste, dass das übergebene Objekt war ein HashSet<T>, dann könnte ich schreiben, eine spezielle,schnellere Methode zu tun, die Kopie,da kann ich ableiten, - Informationen aus diesen, und auch den Zugriff auf die Geschlechtsteile der input-andere HashSet<T>, sonst nur eine "langsame" kopieren
- Ich überprüfte den code, mit Reflektor, gibt es keine solche Optimierung.
- Levesque: keine überraschung, nehme ich an, aber es ist sicherlich eine Optimierung gemacht werden könnte. wie du oben gesagt, es ist ziemlich ineffizient, neu berechnen, etwas, das Sie bereits Zugriff haben, und ich kann nicht denken, der aus irgendeinem Grund nicht zu. dann wieder, wenn Ihr geht zu tun, dass es besser wäre, um eine HashSet<T>(HashSet<T>) - Konstruktor, wie Sie sagte.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn Sie wirklich wollte, dass die effizienteste Methode zum Klonen einer
HashSet<T>
, solltest du Folgendes tun (aber möglicherweise auf Kosten der Wartbarkeit)HashSet<T>
kopiert werden müssen. Sie können dies tun müssen rekursiv für jedes Feld.Reflection.Emit
oder verwenden von expression trees zu erzeugen, eine Methode, die nicht die notwendige kopieren von Feldern. Möglicherweise müssen andere Methoden generiert, die Kopie den Wert jedes Feldes. Wir sind mit runtime code generation, weil es der einzige Weg, um direkt Zugriff auf private Felder.FormatterServices.GetUninitializedObject(...)
instanziiert ein leeres Objekt. Verwenden Sie die generierte Methode in Schritt 2 kopieren Sie die original-Objekt, um die neue leere Objekt.AddWithPresent
hinzufügen. Ich verstehe nicht, warum nichts getan wurde, über diese.FormatterServices.GetUnintializedObject(...)
für etwas anderes als Serialisierung... epic Verwendung von einer wenig bekannten Methode!EDIT: Nach genauerer Betrachtung bedeutet dies nicht, scheint eine gute Idee zu sein, mit weniger als 60 Elementen in der ursprünglichen hashset die Methode unten zu sein scheint langsamer dann nur die Schaffung eines neuen hashset.
HAFTUNGSAUSSCHLUSS: dies scheint zu funktionieren, aber verwenden Sie auf Ihr eigenes Risiko, wenn Sie sich zu serialisieren, die das geklonte hashsets werden Sie wahrscheinlich wollen, zu kopieren SerializationInfo m_siInfo.
Ich auch gegenüber diesem problem und nahm einen Stich an, unten finden Sie eine extension-Methode, die verwendet FieldInfo.GetValue-und SetValue-kopieren Sie die erforderlichen Felder aus. Es ist schneller, als HashSet(IEnumerable), wie viel hängt von der Menge der Elemente in der ursprünglichen hashset. Für 1000 Elementen ist der Unterschied etwa einen Faktor 7. Mit 100.000 Elementen deren etwa um den Faktor 3.
Gibt es andere Möglichkeiten, die vielleicht sogar schneller, aber das hat losgeworden der Engpass bei mir für jetzt. Ich habe versucht, mit expressiontrees und ausgeben, sondern traf eine Straßensperre, wenn ich diese zu arbeiten Ill update this post.
Habe ich überprüft die .NET Framework-Quellcode für beide version 4.5.2 und version 4.7.2.
Version 4.7.2 hat die Optimierung in den Konstruktor zu bewältigen ist, wenn die übergebene Sammlung vom Typ HashSet, durch eine interne Klonen Logik. Sie müssten auch in den comparer in den Konstruktor für diese Logik zu arbeiten. Version 4.5.2 NICHT über diese Optimierung scheint es.
Beispiel:
Einfach Muster, das
solltenicht Arbeit für viele Sammlungen:Leider weiß ich nicht, dass Microsoft alles Tat, um zu verhindern, ruft MemberwiseClone in Orten, wo es sollte nicht aufgerufen werden (z.B. Erklärung etwas anderes als eine Methode, die-wie vielleicht ein Klasse-mit dem Namen MemberwiseClone) also ich weiß nicht, wie man sagen kann, ob ein solcher Ansatz ist wahrscheinlich zu arbeiten.
Ich denke, es ist fair Grund für eine standard-Sammlung nicht zu unterstützen, eine öffentliche Klonen Methode, aber nur ein geschützt eine: es ist möglich, dass eine Klasse, die stammt aus einer Sammlung, bricht stark, wenn geklont wird, und wenn die Basis-Klasse' cloning-Methode ist öffentlich, es gibt keinen Weg, um zu verhindern, dass ein Objekt einer abgeleiteten Klasse an code, der erwartet, dass zu Klonen.
Dass gesagt wurde, es wäre schön gewesen, wenn .net enthalten cloneableDictionary und andere Klassen wie standard-Typen (aber offensichtlich nicht realisiert im wesentlichen wie oben).
O(n) - Klon ist so gut wie es bekommen kann, theoretisch, zu Klonen, zwei Sätze, die nicht den gleichen zugrunde liegenden Datenstruktur.
Überprüfen, ob ein element in einem HashSet werden sollte, eine Konstante Zeit (D. H. O(1)) - operation.
So konnten Sie erstellen einen wrapper, der würde nur wickeln Sie ein vorhandenes HashSet und halten, um alle neuen Ergänzungen, aber das scheint mir ziemlich pervers.
Wenn Sie sagen, "effizient" meinen Sie "effizienter als die bestehende O(n) Methode" - ich postulieren können Sie nicht wirklich effizienter als O(n) ohne zu spielen ziemlich ernst semantische Spiele über das, was "Klonen" bedeutet.
List<T>.Add
hat eine O(1) Komplexität, wieHashSet<T>.Add
, aber es ist viel schneller, weil Sie nicht brauchen, um zu überprüfen, ob das Element bereits vorhanden ist. Also, wenn ich sage "effizient" meine ich schneller, nicht weniger Komplex.Nur ein zufälliger Gedanke. Es könnte albern sein.
Da Sie nicht ICloneable implementieren, und der Konstruktor nicht die Erkenntnis, dass die Quelle ist der gleiche Typ, ich denke, wir sind Links mit einer option. Die Umsetzung der optimierten version, und das hinzufügen von es als eine Erweiterungsmethode für den Typ.
Etwas wie:
Dann der code aus der Frage würde so Aussehen: