用于模糊查找的字典哈希函数

时间:2018-07-05 05:30:22

标签: c# dictionary hash hashtable fuzzy-search

当需要在字符串之间进行近似比较时,基本的Levenshtein Distance可以提供帮助。它测量等于另一个字符串所需的字符串修改量:

"aaaa" vs "aaab" => 1
"abba" vs "aabb" => 2
"aaaa" vs "a"    => 3

使用Dictionary<T, U>时可以提供自定义IEqualityComparer<T>。可以将Levenshtein距离实现为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,字典对此将不胜感激。关于哈希码的一些经验法则,我将使用:

  • 不相等的对象应该不具有相同的哈希码
  • 相等的对象必须具有相同的哈希码

遵循我所能想象的这些规则,唯一可能的哈希函数是一个常数,就像在给定代码中实现的那样。虽然这不是最佳方法,但是例如当我们开始采用字符串的默认哈希值时,aaaaab会以不同的哈希值结束,即使它们被相等地处理。再想一想,这意味着所有可能的字符串都必须具有相同的哈希值。

我正确吗?为什么当我将默认的字符串哈希函数与哈希冲突一起用于我们的比较器时,字典的性能会更好吗? 这是否会使字典中的哈希桶无效?

public int GetHashCode(string obj)
    => obj.GetHashCode();

2 个答案:

答案 0 :(得分:2)

我认为没有适合您情况的哈希函数。

问题在于,您只能根据信号值分配存储桶,而您不知道之前添加了什么。但是,要散列的项的Levenshtein距离可以是从0到“无穷大”的任何值,唯一重要的是将其与之进行比较。因此,您不能满足哈希函数的第二个条件(要使相等的对象具有相同的哈希码)。

当您想要最大距离为2 并且您在词典中已经有两个项目“伪证明” >,它们之间的相互距离为3 。如果然后添加一个字符串,该字符串与第一个项目的距离为2,而与第二个项目的距离为1,那么您将如何确定该字符串应与哪个项目匹配?它满足您对这两个项目的最大值,但可能应该与第二个而不是第一个匹配。但是对字典的内容一无所知,您不知道如何正确对其进行哈希处理。

对于第二个问题-使用默认的string.GetHashCode()方法确实可以提高性能,但是会破坏相等比较器的功能。如果在示例代码上测试此解决方案,则可以看到dict现在将包含两个键。这是因为GetHashCode返回了两个不同的哈希码,因此没有冲突,dict现在有两个存储桶,并且您的Equals方法甚至没有执行。

答案 1 :(得分:0)

我可以理解模糊查找。但不是模糊存储。为“ aab”分配值时,为什么要覆盖“ aaa”?如果您只想进行模糊查找,那么拥有一本普通词典并进行诸如...的模糊查找扩展会更好。

public static class DictionaryExtensions
{
    private static IEqualityComparer<string> _comparer = new LevenshteinStringComparer(distance);

    public static IEnumerable<T> FuzzyMatch<T>(this IDictionary<string, T> dictionary, string key, int distance = 2)
    {
        return dictionary
            .Keys
            .Where(k => _comparer.Equals(k, key))
            .Select(k => dictionary[k]);
    }
}

这不仅仅是评论,还是答案。要回答您的问题,请考虑以下示例...

"abba" vs "cbbc" => 2
"cddc" vs "cbbc" => 2
"abba" vs "cddc" => 4

这里有要点吗?即很明显,不可能做到以下几点

abba == cbbc && 
cddc == cbbc &&
abba != cddc