TL; Я ищу способ получить IEqualityComparer<T>
от IComparer<T>
, независимо от того, какой тип данных T
, включая варианты без учета регистра, если T
is string
. Или мне нужно другое решение для этой проблемы.Есть ли способ получить IEqualityComparer от IComparer?
Полный текст статьи: Я реализую простой общий кэш с политикой LFU. Требование состоит в том, что должно быть возможно выбрать, будет ли кеш чувствителен к регистру или нечувствителен к регистру - если string
является типом данных для ключей кеша (что необязательно). В решении я в первую очередь разрабатываю кэш, я ожидаю сотни миллиардов запросов кэш-памяти и размеры кэша не более 100 000 записей. Из-за этих чисел я сразу же отказался от использования любой строковой манипуляции, которая вызывает выделения (например, .ToLower().GetHashCode()
и т. Д.), И вместо этого предпочла использовать IComparer
и IEqualityComparer
, поскольку они являются стандартными функциями BCL. Пользователь этого кеша может передавать сравнения с конструктором. Вот соответствующие фрагменты кода:
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));
}
// ...
}
keyEqualityComparer
используется для управления записью кэша (так, например, ключ «ABC» и «а» равен, если пользователь хочет). Код keyComparer
используется для управления элементами кэша, отсортированными по UseCount
, так что легко выбрать наименее часто используемый (реализованный в классе CacheItemComparer
).
Пример правильное использование с пользовательскими сравнения:
var cache = new LFUCache<string, int>(10000,
StringComparer.InvariantCultureIgnoreCase,
StringComparer.InvariantCultureIgnoreCase);
(Это выглядит глупо, но StringComparer
реализует как IComparer<string>
и IEqualityComparer<string>
.) Проблема заключается в том, что если пользователь дает Несовместимые компараторов (т.е. без учета регистра keyEqualityComparer
и Учитывать keyComparer
), то наиболее вероятным результатом является недопустимая статистика LFU, и, таким образом, в лучшем случае будет снижаться кеш. Другой сценарий также меньше желаемого. Также, если ключ более изощрен (я буду иметь что-то похожее на Tuple<string,DateTime,DateTime>
), его можно более серьезно испортить.
Вот почему я хотел бы иметь только один аргумент сравнения в конструкторе, но это не работает. Я могу создать IEqualityComparer<T>.Equals()
с помощью IComparer<T>.Compare()
, но я застрял в IEqualityComparer<T>.GetHashCode()
- что очень важно, как вы знаете. Если бы я получил доступ к приватным свойствам компаратора, чтобы проверить, чувствителен ли он к регистру или нет, я бы использовал CompareInfo
для получения хэш-кода.
Мне нравится этот подход с двумя различными структурами данных, поскольку он дает мне приемлемую производительность и контролируемое потребление памяти - на моем ноутбуке около 500 000 добавлений кеша/сек с размером кеша 10.000 элементов. Dictionary<TKey,TValue>
используется только для поиска данных в O (1), а SortedSet<CacheItem>
вставляет данные в O (log n), найдите элемент для удаления, вызывая lfuList.Min
в O (log n), и найдите запись, чтобы увеличить счетчик использования также в O (log n).
Любые предложения относительно того, как решить это, приветствуются. Я буду признателен за любые идеи, в том числе разные проекты.
Одна из возможностей заключается в использовании общих ограничений для определения статического заводского метода, который принимает один параметр сравнения, который реализует как «IEqualityComparer», так и «IComparer ». Тогда, по крайней мере, вы не проходите в одном объекте с двумя разными параметрами. –
Это звучит интересно, однако каким-то образом я не могу понять, как должен выглядеть код. Можете ли вы поделиться несколькими черными строками кода? ;-) – Endrju
Несомненно. См. Мой ответ. –