TL DR I'm looking for a way to get IEqualityComparer<T> from IComparer<T> , no matter what data type T , including case-insensitive options if T is a string . Or I need another solution for this problem.
Here's the full story: I implement a simple shared cache with LFU policy. The requirement is that it should be possible to choose whether the cache is case sensitive or case insensitive - if string is a data type for cache keys (which is optional). In the solution, I primarily develop the cache, I expect hundreds of billions of cache requests and cache sizes of not more than 100,000 entries. Because of these numbers, I immediately refused to use any string manipulation that causes the selection (e.g. .ToLower().GetHashCode() , etc.) and instead chose to use IComparer and IEqualityComparer , since they are standard BCL functions . The user of this cache can pass comparisons with the constructor. Here are the relevant code snippets:
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)
keyEqualityComparer used to manage cache entries (for example, the "ABC" and "abc" keys are equal if the user wants). keyComparer used to manage cache items sorted by UseCount , so it’s easy to select the least frequently used (implemented in the CacheItemComparer class).
An example of proper use with custom mapping:
var cache = new LFUCache<string, int>(10000, StringComparer.InvariantCultureIgnoreCase, StringComparer.InvariantCultureIgnoreCase);
(This looks silly, but StringComparer implements both IComparer<string> and IEqualityComparer<string> ). The problem is that if the user gives incompatible mappings (i.e., case insensitivity keyEqualityComparer and case sensitivity keyComparer ), then the most likely result is invalid LFU statistics and, as a result, lower cache level. Another scenario is also less than desired. In addition, if the key is more complex (I will have something similar to Tuple<string,DateTime,DateTime> ), you can spoil it more seriously.
This is why I would like to have only one comparison argument in the constructor, but this does not seem to work. I can create IEqualityComparer<T>.Equals() using IComparer<T>.Compare() , but I'm stuck in IEqualityComparer<T>.GetHashCode() - this is very important, as you know. If I had access to private comparison properties to check if it is case sensitive or not, I would use CompareInfo to get the hash code.
I like this approach with two different data structures because it gives me acceptable performance and controlled memory consumption - on my laptop about 500,000 cache additions / sec with a cache size of 10.000. Dictionary<TKey,TValue> used only to search for data in O (1), and SortedSet<CacheItem> inserts data into O (log n), finds the element to delete, calling lfuList.Min in O (log n), and finds the entry to increase use count also in O (log n).
Any suggestions on how to solve this problem are welcome. I will be grateful for any ideas, including various projects.