TL; DR Ich bin auf der Suche nach einer Möglichkeit, IEqualityComparer<T>
von IComparer<T>
, egal welcher Datentyp ist T
, einschließlich der Groß- und Kleinschreibung Optionen, wenn T
ist string
. Oder ich brauche eine andere Lösung für dieses Problem.Gibt es eine Möglichkeit, IEqualityComparer von IComparer abzuleiten?
Hier ist die ganze Geschichte: Ich implementiere einfachen, generischen Cache mit LFU-Richtlinie. Voraussetzung ist, dass ausgewählt werden kann, ob im Cache die Groß-/Kleinschreibung beachtet wird oder ob die Groß-/Kleinschreibung nicht beachtet wird - wenn string
der Datentyp für Cache-Schlüssel ist (was nicht notwendig ist). In der Lösung, für die ich hauptsächlich den Cache entwickle, erwarte ich Hunderte von Milliarden Cache-Lookups und Cachegrößen von maximal 100.000 Einträgen. Wegen dieser Zahlen habe ich sofort auf die Verwendung von String-Manipulationen verzichtet, die Zuweisungen verursachen (wie .ToLower().GetHashCode()
usw.) und stattdessen die Verwendung von IComparer
und IEqualityComparer
als Standard-BCL-Funktionen gewählt. Der Benutzer dieses Caches kann die Vergleiche an den Konstruktor übergeben. Hier relevante Fragmente des Codes sind:
public class LFUCache<TKey,TValue>
{
private readonly Dictionary<TKey,CacheItem> entries;
private readonly SortedSet<CacheItem> lfuList;
private class CacheItem
{
public TKey Key;
public TValue Value;
public int UseCount;
}
private class CacheItemComparer : IComparer<CacheItem>
{
private readonly IComparer<TKey> cacheKeyComparer;
public CacheItemComparer(IComparer<TKey> cacheKeyComparer)
{
this.cacheKeyComparer = cacheKeyComparer;
if (cacheKeyComparer == null)
this.cacheKeyComparer = Comparer<TKey>.Default;
}
public int Compare(CacheItem x, CacheItem y)
{
int UseCount = x.UseCount - y.UseCount;
if (UseCount != 0) return UseCount;
return cacheKeyComparer.Compare(x.Key, y.Key);
}
}
public LFUCache(int capacity, IEqualityComparer<TKey> keyEqualityComparer,
IComparer<TKey> keyComparer) // <- here's my problem
{
// ...
entries = new Dictionary<TKey, CacheItem>(keyEqualityComparer);
lfuList = new SortedSet<CacheItem>(new CacheItemComparer(keyComparer));
}
// ...
}
Die keyEqualityComparer
verwendet wird Cache-Einträge zu verwalten (so beispielsweise die Taste „ABC“ und „abc“ gleich ist, wenn der Benutzer möge). Die keyComparer
wird verwendet, um Cache-Einträge zu verwalten, die nach UseCount
sortiert sind, so dass es einfach ist, die am wenigsten häufig verwendeten auszuwählen (implementiert in der Klasse CacheItemComparer
).
Beispiel korrekte Verwendung mit benutzerdefinierten Vergleich:
var cache = new LFUCache<string, int>(10000,
StringComparer.InvariantCultureIgnoreCase,
StringComparer.InvariantCultureIgnoreCase);
(Das sieht blöd, aber StringComparer
implementiert sowohl IComparer<string>
und IEqualityComparer<string>
.) Das Problem ist, dass, wenn der Benutzer gibt unvereinbar comparers (dh Groß- und Kleinschreibung keyEqualityComparer
und Groß- und Kleinschreibung keyComparer
), dann ist das wahrscheinlichste Ergebnis eine ungültige LFU-Statistik und somit niedrigere Cache-Treffer im besten Fall. Das andere Szenario ist auch weniger als gewünscht. Auch wenn der Schlüssel ausgefeilter ist (ich werde etwas haben, das an Tuple<string,DateTime,DateTime>
erinnert), ist es möglich, es schwerer durcheinander zu bringen.
Deshalb möchte ich nur ein einziges Vergleichsargument im Konstruktor haben, aber das scheint nicht zu funktionieren. Ich kann IEqualityComparer<T>.Equals()
mit Hilfe von IComparer<T>.Compare()
erstellen, aber ich bin bei IEqualityComparer<T>.GetHashCode()
stecken - das ist sehr wichtig, wie Sie wissen. Wenn ich Zugriff auf private Eigenschaften des Vergleichers hatte, um zu prüfen, ob die Groß-/Kleinschreibung beachtet wird oder nicht, hätte ich CompareInfo
verwendet, um Hash-Code zu erhalten.
Ich mag diesen Ansatz mit 2 verschiedenen Datenstrukturen, weil er mir akzeptable Leistung und kontrollierbaren Speicherverbrauch bietet - auf meinem Laptop rund 500.000 Cache-Ergänzungen/Sek. Mit Cache-Größe 10.000 Elemente. Dictionary<TKey,TValue>
wird nur verwendet, um Daten in O (1) zu finden, und SortedSet<CacheItem>
fügt Daten in O (log n) ein, finde zu entfernendes Element durch Aufrufen von lfuList.Min
in O (log n) und findet den Eintrag zum Inkrementieren der Verwendungszählung auch in O (log n).
Alle Vorschläge zur Lösung dieses Problems sind willkommen. Ich werde alle Ideen, einschließlich verschiedener Designs, zu schätzen wissen.
Eine Möglichkeit besteht darin, generische Constraints zu verwenden, um eine statische Factory-Methode zu definieren, die einen einzigen Comparer-Parameter verwendet, der sowohl IEqualityComparer 'als auch' IComparer 'implementiert. Dann haben Sie zumindest nicht dasselbe Objekt an zwei verschiedene Parameter übergeben. –
Das klingt interessant, aber irgendwie kann ich mir nicht vorstellen, wie der Code aussehen soll. Können Sie ein paar grobe Codezeilen teilen? ;-) – Endrju
Sicher. Siehe meine Antwort. –