Когда требуется приблизительное сравнение между строками, может помочь базовое Расстояние Левенштейна . Он измеряет количество модификаций строки, необходимых для совпадения с другой строкой:
"aaaa" vs "aaab" => 1
"abba" vs "aabb" => 2
"aaaa" vs "a" => 3
При использовании Dictionary<T, U>
можно указать пользовательский IEqualityComparer<T>
. Расстояние Левенштейна можно реализовать как IEqualityComparer<string>
:
public class LevenshteinStringComparer : IEqualityComparer<string>
{
private readonly int _maximumDistance;
public LevenshteinStringComparer(int maximumDistance)
=> _maximumDistance = maximumDistance;
public bool Equals(string x, string y)
=> ComputeLevenshteinDistance(x, y) <= _maximumDistance;
public int GetHashCode(string obj)
=> 0;
private static int ComputeLevenshteinDistance(string s, string t)
{
// Omitted for simplicity
// Example can be found here: https://www.dotnetperls.com/levenshtein
}
}
Итак, мы можем использовать нечеткий словарь:
var dict = new Dictionary<string, int>(new LevenshteinStringComparer(2));
dict["aaa"] = 1;
dict["aab"] = 2; // Modify existing value under "aaa" key
// Only one key was created:
dict.Keys => { "aaa" }
Имея все эти настройки, вы, возможно, заметили, что мы не реализовали правильный GetHashCode
в LevenshteinStringComparer
, который был бы высоко оценен словарем. Как правило, что касается хеш-кодов, я бы использовал:
- Неравные объекты должны не иметь одинаковый хэш-код
- Равные объекты должны иметь одинаковый хэш-код
Единственная возможная хеш-функция, соответствующая этим правилам, которую я могу себе представить, - это постоянное число, как это реализовано в данном коде. Это не оптимально, но когда мы начнем, например, брать хэш строки по умолчанию, тогда aaa
и aab
будут заканчиваться разными хешами, даже если они обрабатываются как равные. Если подумать дальше, это означает, что все возможные строки должны иметь одинаковый хэш.
Я прав? И почему производительность словаря улучшается, когда я использую стандартную хеш-функцию с коллизиями хеша для нашего компаратора? Разве это не делает недопустимыми хэш-блоки внутри словаря?
public int GetHashCode(string obj)
=> obj.GetHashCode();