Case Insensitive Dictionary

Case insensitive access for generic dictionary

There's no way to specify a StringComparer at the point where you try to get a value. If you think about it, "foo".GetHashCode() and "FOO".GetHashCode() are totally different so there's no reasonable way you could implement a case-insensitive get on a case-sensitive hash map.

You can, however, create a case-insensitive dictionary in the first place using:-

var comparer = StringComparer.OrdinalIgnoreCase;
var caseInsensitiveDictionary = new Dictionary<string, int>(comparer);

Or create a new case-insensitive dictionary with the contents of an existing case-sensitive dictionary (if you're sure there are no case collisions):-

var oldDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var newDictionary = new Dictionary<string, int>(oldDictionary, comparer);

This new dictionary then uses the GetHashCode() implementation on StringComparer.OrdinalIgnoreCase so comparer.GetHashCode("foo") and comparer.GetHashcode("FOO") give you the same value.

Alternately, if there are only a few elements in the dictionary, and/or you only need to lookup once or twice, you can treat the original dictionary as an IEnumerable<KeyValuePair<TKey, TValue>> and just iterate over it:-

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var value = myDictionary.FirstOrDefault(x => String.Equals(x.Key, myKey, comparer)).Value;

Or if you prefer, without the LINQ:-

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
int? value;
foreach (var element in myDictionary)
{
if (String.Equals(element.Key, myKey, comparer))
{
value = element.Value;
break;
}
}

This saves you the cost of creating a new data structure, but in return the cost of a lookup is O(n) instead of O(1).

Case insensitive dictionary

If I understand you correctly and you want a way to key dictionaries in a non case-sensitive fashion, one way would be to subclass dict and overload the setter / getter:

class CaseInsensitiveDict(dict):
def __setitem__(self, key, value):
super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)

def __getitem__(self, key):
return super(CaseInsensitiveDict, self).__getitem__(key.lower())

Case-INsensitive Dictionary with string key-type in C#

This seemed related, but I didn't understand it properly: c# Dictionary: making the Key case-insensitive through declarations

It is indeed related. The solution is to tell the dictionary instance not to use the standard string compare method (which is case sensitive) but rather to use a case insensitive one. This is done using the appropriate constructor:

var dict = new Dictionary<string, YourClass>(
StringComparer.InvariantCultureIgnoreCase);

The constructor expects an IEqualityComparer which tells the dictionary how to compare keys.

StringComparer.InvariantCultureIgnoreCase gives you an IEqualityComparer instance which compares strings in a case-insensitive manner.

Case insensitive dictionary search?

Note that making a dictionary case-insensitive, by whatever mean, may well lose information: for example, how would you "case-insensitivize" {'a': 23, 'A': 45}?! If all you care is where a key is in the dict or not (i.e., don't care about what value corresponds to it), then make a set instead -- i.e.

theset = set(k.lower() for k in thedict)

(in every version of Python, or {k.lower() for k in thedict} if you're happy with your code working only in Python 2.7 or later for the sake of some purely decorative syntax sugar;-), and check with if k.lower() in theset: ....

Or, you could make a wrapper class, e.g., maybe a read-only one...:

import collections

class CaseInsensitiveDict(collections.Mapping):
def __init__(self, d):
self._d = d
self._s = dict((k.lower(), k) for k in d)
def __contains__(self, k):
return k.lower() in self._s
def __len__(self):
return len(self._s)
def __iter__(self):
return iter(self._s)
def __getitem__(self, k):
return self._d[self._s[k.lower()]]
def actual_key_case(self, k):
return self._s.get(k.lower())

This will keep (without actually altering the original dictionary, so all precise information can still be retrieve for it, if and when needed) an arbitrary one of possibly-multiple values for keys that "collapse" into a single key due to the case-insensitiveness, and offer all read-only methods of dictionaries (with string keys, only) plus an actual_key_case method returning the actual case mix used for any given string key (or None if no case-alteration of that given string key matches any key in the dictionary).

Case insensitive dictionary in TypeScript

At runtime you can get fairly close to what you're asking for using a Proxy.

TypeScript can't do much to help with strongly typing the keys past just string, since the type system does not have the ability to represent the effect of string operations on string literal types. So if you wanted an object with keystring as a known key, the compiler would have no idea that KEYSTRING was also a known key. There are suggestions for manipulating string literal types, such as using regular expressions on them, but nothing is currently part of the language. Luckily for you, I think, you're asking for a dictionary type, which usually means the keys are just string. As long as you don't expect the compiler to keep track of valid keys for you, you'll be okay.

Here's a possible implementation of using a Proxy. Note that I haven't completely tested every possible edge case, but the basic idea is to get in the way of everything that accepts a property key and replace it with a canonical version:

function dictWithCanonicalKeys<V>(canon: (prop: keyof any) => keyof any) {
return new Proxy<{ [k: string]: V }>(
{},
{
get(target, prop) {
return Reflect.get(target, canon(prop));
},
set(target, prop, value) {
return Reflect.set(target, canon(prop), value);
},
has(target, prop) {
return Reflect.has(target, canon(prop));
},
defineProperty(target, prop, attribs) {
return Reflect.defineProperty(target, canon(prop), attribs);
},
deleteProperty(target, prop) {
return Reflect.deleteProperty(target, canon(prop));
},
getOwnPropertyDescriptor(target, prop) {
return Reflect.getOwnPropertyDescriptor(target, canon(prop));
}
}
);
}

And let's use it, passing in a canonicalizing function that converts the key to lowercase (if it's a string, that is... it leaves symbol keys alone):

const dict = dictWithCanonicalKeys<number | undefined>(
p => (typeof p === "string" ? p.toLowerCase() : p)
);

dict.keyString = 1;
console.log(dict.KEYSTRING); // 1
dict.KEYstring = "two"; // error! "two" is not a number
dict.KEYstring = 2;
console.log(dict.keyStrinG); // 2
console.log(JSON.stringify(dict)); // {"keystring": 2};
console.log("KeyString" in dict); // true
Object.keys(dict).forEach(k => console.log(k + ":" + dict[k])); // keystring:2

That all looks reasonable to me.


Finally, a caveat: you should be careful introducing things like this to your code. It would be quite surprising to most developers to find that their supposedly plain objects have case-insensitive keys (console.log(Object.keys(dict).indexOf("keyString")); // -1?!), and even if you can explain it to developers, their IDEs will not understand and might flag a key that differs in case from an expected one (e.g., KeyString) as an error. If you're doing this to deal with user input, it might be cleaner to just define strict barriers where the input is allowed into your program, and canonicalize the case there, so the dictionary can be blissfully unaware and be implemented as a plain object.

Of course, only you know your use case, and maybe a case-insensitive dictionary-like object is the right choice. If so, I hope the above code helps you. Good luck!

Link to code

Case-insensitive dictionary check with lower()

The problem is that you're comparing a lowercase rolename with dictionary keys that are not in lowercase. For a case-insensitive check, both the rolename and dictionary keys should be lowercase.

Either manually change the dictionary keys to lowercase:

role_dict = {
"members":557212810468392970,
"ps4":568761643916328960,
"lol":559792606364565505}

Or create it programmatically with a dict comprehension, and check if rolename.lower() is in the lowercase dict:

role_dict = {
"Members":557212810468392970,
"PS4":568761643916328960,
"LoL":559792606364565505}
lowercase_dict = {k.lower():v for k,v in role_dict.items()}
role_id = lowercase_dict.get(rolename.lower())
if not role_id:
await ctx.send("I cannot find the role {}.".format(rolename))
return

O(1) Dictionary both case sensitive and insensitive

I recently ran across this question since I needed to solve for the same scenario, with the following guidelines:

  • Both types of lookups should be fast (avoid O(n) for case-sensitive path).
  • Use a single dictionary rather than two separate dictionaries (avoid double the space requirement).
  • Avoid needless allocations via string conversion (.ToLower()) when performing lookups.
  • Trade a reasonable amount of additional needed memory for satisfying the above.

I didn't feel that any of the current answers met the above and, as a result, I came up with the following dictionary definition:

Dictionary<string, (string Key, T Value)> _dict = 
new Dictionary<string, (string Key, T Value)>(StringComparer.OrdinalIgnoreCase);

This allows for case-insensitive hashing of the key, while using a ValueTuple to store the actual key in its raw string form for additional case-sensitive comparisons, if need be, alongside the value. The full implementation looks like the following:

public class MultiCaseDictionary<T>
{
public MultiCaseDictionary() : this(0) { }

public MultiCaseDictionary(int capacity)
{
_dict = new(capacity, StringComparer.OrdinalIgnoreCase);
}

private readonly Dictionary<string, (string Key, T Value)> _dict;

public bool TryGetValue(string key, out T value, bool isCaseInsensitive = true)
{
if (_dict.TryGetValue(key, out (string Key, T Value) match))
{
if (isCaseInsensitive)
{
value = match.Value;
return true;
}

if (key.Equals(match.Key))
{
value = match.Value;
return true;
}
}

value = default;
return false;
}

public void Add(string key, T value)
{
_dict[key] = (key, value);
}
}

This structure results in excellent performance for both the case sensitive and insensitive paths, and the case-sensitive path is slightly slower than the case-sensitive (optimal) path in the accepted answer due to needing to perform extra work.

However, it is orders of magnitude faster for the case-insensitive path, due to the accepted answer having a runtime complexity of O(n). In addition, the case-insensitive search of MultiCaseDictionary is actually faster than the case-sensitive search of CaseSensitiveDictionary.

Finally, the Add path is slower in the MultiCaseDictionary due to needing to construct a ValueTuple. The following benchmark demonstrates both implementations, running on .NET 6:

Implementations

public class MultiCaseDictionary<T>
{
public MultiCaseDictionary() : this(0) { }

public MultiCaseDictionary(int capacity)
{
_dict = new(capacity, StringComparer.OrdinalIgnoreCase);
}

private readonly Dictionary<string, (string Key, T Value)> _dict;

public bool TryGetValue(string key, out T value, bool isCaseInsensitive = true)
{
if (_dict.TryGetValue(key, out (string Key, T Value) match))
{
if (isCaseInsensitive)
{
value = match.Value;
return true;
}

if (key.Equals(match.Key))
{
value = match.Value;
return true;
}
}

value = default;
return false;
}

public void Add(string key, T value)
{
_dict[key] = (key, value);
}
}

public class CaseSensitiveDictionary<T>
{
public CaseSensitiveDictionary() : this(0) { }

public CaseSensitiveDictionary(int capacity)
{
_dict = new(capacity);
}

private readonly Dictionary<string, T> _dict;

public bool TryGetValue(string key, out T value, bool isCaseInsensitive = true)
{
if (!isCaseInsensitive)
{
if (_dict.TryGetValue(key, out value))
{
return true;
}
}
else
{
key = _dict.Keys.FirstOrDefault(k =>
string.Compare(key, k, StringComparison.OrdinalIgnoreCase) == 0);
if (key == null)
{
value = default;
return false;
}

value = _dict[key];
return true;
}

value = default;
return false;
}

public void Add(string key, T value)
{
_dict[key] = value;
}
}

Benchmarks

The benchmarks preload 1000 entries for TryGetValue tests to demonstrate O(n) behavior. In addition, they insert 10 entries for Add tests to check space requirements and add performance degradation.

[SimpleJob(RuntimeMoniker.Net60, invocationCount: 50000)]
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class CaseSensitiveDictionaryBenchmarks
{
// use longer key to simulate expected hash costs
private const string Key = "100000500";
private const int Capacity = 1000;
private const int Iterations = 9;

private MultiCaseDictionary<int> _multiCaseDictionary;
private CaseSensitiveDictionary<int> _caseSensitiveDictionary;

[IterationSetup(Targets = new string[] {
nameof(MultiCaseDictionary_Search),
nameof(MultiCaseDictionary_InsensitiveSearch)
})]
public void SetupMultiCase()
{
_multiCaseDictionary = new MultiCaseDictionary<int>(Capacity);
for (int i = 100000000; i < 100001000; i++)
{
_multiCaseDictionary.Add(i.ToString(), i);
}
}

[IterationSetup(Targets = new string[] {
nameof(CaseSensitiveDictionary_Search),
nameof(CaseSensitiveDictionary_InsensitiveSearch),
})]
public void SetupCaseSensitive()
{
_caseSensitiveDictionary = new CaseSensitiveDictionary<int>(Capacity);
for (int i = 100000000; i < 100001000; i++)
{
_caseSensitiveDictionary.Add(i.ToString(), i);
}
}

[Benchmark(Baseline = true), BenchmarkCategory("TryGetValue")]
public int CaseSensitiveDictionary_Search()
{
int result;
for (int i = 0; i < Iterations; i++)
{
_caseSensitiveDictionary.TryGetValue(Key, out result, false);
}
_caseSensitiveDictionary.TryGetValue(Key, out result, false);
return result;
}

[Benchmark, BenchmarkCategory("TryGetValue")]
public int CaseSensitiveDictionary_InsensitiveSearch()
{
int result;
for (int i = 0; i < Iterations; i++)
{
_caseSensitiveDictionary.TryGetValue(Key, out result, true);
}
_caseSensitiveDictionary.TryGetValue(Key, out result, true);
return result;
}

[Benchmark, BenchmarkCategory("TryGetValue")]
public int MultiCaseDictionary_Search()
{
int result;
for (int i = 0; i < Iterations; i++)
{
_multiCaseDictionary.TryGetValue(Key, out result, false);
}
_multiCaseDictionary.TryGetValue(Key, out result, false);
return result;
}

[Benchmark, BenchmarkCategory("TryGetValue")]
public int MultiCaseDictionary_InsensitiveSearch()
{
int result;
for (int i = 0; i < Iterations; i++)
{
_multiCaseDictionary.TryGetValue(Key, out result, true);
}
_multiCaseDictionary.TryGetValue(Key, out result, true);
return result;
}

[Benchmark(Baseline = true), BenchmarkCategory("Add")]
public CaseSensitiveDictionary<int> CaseSensitiveDictonary_Add()
{
_caseSensitiveDictionary = new CaseSensitiveDictionary<int>(Capacity);
for (int i = 100000000; i < 100000010; i++)
{
_caseSensitiveDictionary.Add(i.ToString(), i);
}

return _caseSensitiveDictionary;
}

[Benchmark, BenchmarkCategory("Add")]
public MultiCaseDictionary<int> MultiCaseDictionary_Add()
{
_multiCaseDictionary = new MultiCaseDictionary<int>(Capacity);
for (int i = 100000000; i < 100000010; i++)
{
_multiCaseDictionary.Add(i.ToString(), i);
}

return _multiCaseDictionary;
}
}

Results
benchmarkresults


BenchmarkDotNet=v0.13.2, OS=macOS Catalina 10.15.7 (19H1417) [Darwin 19.6.0]
Intel Core i5-1038NG7 CPU 2.00GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.400
[Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
Job-NQOSHT : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2

Runtime=.NET 6.0 InvocationCount=50000















































































































MethodCategoriesMeanErrorStdDevMedianRatioRatioSDGen0AllocatedAlloc Ratio
CaseSensitiveDictonary_AddAdd2,423.0 ns24.67 ns20.60 ns2,418.3 ns1.000.0010.000031440 B1.00
MultiCaseDictionary_AddAdd3,100.2 ns61.03 ns59.94 ns3,109.2 ns1.270.0212.820040264 B1.28
CaseSensitiveDictionary_SearchTryGetValue251.8 ns6.47 ns18.67 ns246.1 ns1.000.000.0600240 B1.00
CaseSensitiveDictionary_InsensitiveSearchTryGetValue95,573.4 ns1,888.58 ns2,768.26 ns95,401.3 ns378.7729.360.40001280 B5.33
MultiCaseDictionary_SearchTryGetValue265.5 ns5.11 ns10.43 ns262.2 ns1.050.07--0.00
MultiCaseDictionary_InsensitiveSearchTryGetValue233.1 ns4.83 ns14.16 ns226.2 ns0.930.09--0.00

Access python dictionary with case insensitive string

You need to handle case-insenstivity on your end by either converting your dictionary.keys() to lower case or list elements to lower case or both as:

for item in list:
if item.lower() in map(str.lower, dictionary.keys()):
print(item)

You can either use lower() or upper(), but you have to be consistent in both the cases.

Case-Insensitive dictionary not working as expected

The dictionary is defined as case-insensitive, but then you are overwriting it with a case-sensitive one.

// definition 
Dictionary<string, PIPoint> PointsDictionary = new Dictionary<string, PIPoint>(StringComparer.CurrentCultureIgnoreCase);

// later *reassignment*
PointsDictionary = RegisteredPoints.ToDictionary(x => x.Name, x => x);

You see, you're replacing the value! When you overwrite the value, the original comparer is lost. You need to specify the comparer again when you call ToDictionary. Luckily there is an overload that takes a key comparer:

PointsDictionary = RegisteredPoints.ToDictionary(x => x.Name, x => x, StringComparer.CurrentCultureIgnoreCase);

Note there are also overloads of ToDictionary that take only the key selector (and comparer), so you can simplify to:

PointsDictionary = RegisteredPoints.ToDictionary(x => x.Name, StringComparer.CurrentCultureIgnoreCase);

You can even pass the original Comparer so you don't have to remember exactly what type it was:

PointsDictionary = RegisteredPoints.ToDictionary(x => x.Name, PointsDictionary.Comparer);

Alternatively, you could clear the dictionary and the re-add all the items to it which will preserve the original comparer:

PointsDictionary.Clear():
foreach(var p in RegisteredPoints)
PointsDictionary[p.Name] = p:


Related Topics



Leave a reply



Submit