C# 与加权字段进行模糊比较(建议类似实例)

C# 与加权字段进行模糊比较(建议类似实例),c#,fuzzy-search,weighted,search-suggestion,C#,Fuzzy Search,Weighted,Search Suggestion,今天我遇到了一个特定的任务,并喜欢用干净的代码来解决它,所以我决定与全班同学分享它是很酷的——但是,嘿,让我们把它保持在一个问题的形式 任务: 给定一个T类型的实例(源代码)和一组T类型的实例(可能的建议), 提供与源相似的建议,按相似性排序,并完全排除其相似性低于某个阈值的建议 相似性将模糊字符串比较实例的多个字段,每个字段具有一个重要权重 输入示例: 源实例: {A = "Hello", B = "World", C = "and welco

今天我遇到了一个特定的任务,并喜欢用干净的代码来解决它,所以我决定与全班同学分享它是很酷的——但是,嘿,让我们把它保持在一个问题的形式

任务: 给定一个
T
类型的实例(源代码)和一组
T
类型的实例(可能的建议), 提供与源相似的建议,按相似性排序,并完全排除其相似性低于某个阈值的建议

相似性将模糊字符串比较实例的多个字段,每个字段具有一个重要权重

输入示例: 源实例:

{A = "Hello", B = "World", C = "and welcome!"}
可能的建议:

{A = "Hola", B = "World", C = "Welcome!"}
{A = "Bye", B = "world", C = "and fairwell"}
{A = "Hell", B = "World", C = "arrives..."}
{A = "Hello", B = "Earth", C = "and welcome!"}
{A = "Hi", B = "world", C = "welcome!"}
字段的重要性:

  • A:30%
  • B:50%
  • C:20%
示例输出: 请注意,可能的建议
Bye;世界fairwell根本不在这里,因为它不满足最小相似性阈值(假设阈值至少为
50%
加权相似性)

第一个结果与源极为相似,尽管
C
字段与源极不相似,因为我们给
C
的权重低至
20%
,另外两个权重更大的字段与源极为相似(或完全匹配)

模糊比较侧注

用于比较
字符串a
字符串b
的算法可以是任何已知的模糊比较算法,这并不是本文的重点


那么如何才能将可能的建议列表转化为实际的有序建议列表呢?(哦,上帝,请帮助,等等)

对于我们的案例,让我们使用可怕的算法

因此,假设我们有一个具有以下签名的函数:

private static int CalcLevenshteinDistance(string a, string b)
为了实际获得
a
b
之间的相似性,而不是距离,我们将使用:

private static decimal CalcLevenshteinSimilarity(string a, string b)
{
    return 1 - ((decimal)CalcLevenshteinDistance(a, b) /
                Math.Max(a.Length, b.Length));
}
如果字符串完全相同,则返回
1
;如果字符串完全不相似,或介于两者之间,则返回
0
。例如,
0.89
将是
a
b
相似的
89%
(不错!)

为了帮助我们处理加权字段,让我们创建一个小助手类:

public class SuggestionField
{
    public string SourceData { get; set; }
    public string SuggestedData { get; set; }
    public decimal Importance { get; set; }
}
这将表示将
T
类型的单个字段与源
T
实例匹配所需的所有信息

现在,计算单个建议和来源之间的加权相似性相当简单:

private static decimal RateSuggestion(IEnumerable<SuggestionField> fields)
{
    return fields.Sum(x =>
        x.Importance * CalcLevenshteinSimilarity(x.SourceData,
                                                 x.SuggestedData));
}
好吧,好吧,这段代码乍一看可能有点让人困惑,但请放松。 主要的混淆可能来自
params Func[]fieldSelectors
,因此也来自
Similarity=rate建议(fieldSelectors.Select(f=>f(x))

对于那些对Linq和所有那些带有选择器的游戏很在行的人来说,可能已经理解了如何使用该功能。在任何情况下,这将是明确的在一瞬间

用法: 这对您来说可能看起来不错,或者可以对其进行修改,使其具有更符合您喜好的用法,但我希望这个想法是明确的,并且有人会发现它很有用;)

p.S

当然,相似性阈值可以作为参数传入。 请随意添加任何想法和评论,以使其更好或更具可读性

private static decimal RateSuggestion(IEnumerable<SuggestionField> fields)
{
    return fields.Sum(x =>
        x.Importance * CalcLevenshteinSimilarity(x.SourceData,
                                                 x.SuggestedData));
}
public static IEnumerable<T> Suggest<T>
    (IEnumerable<T> possibleSuggestions,
     params Func<T, SuggestionField>[] fieldSelectors)
{
    return possibleSuggestions
        .Select(x => new
                     {
                         Suggestion = x,
                         Similarity = RateSuggestion(fieldSelectors.Select(f => f(x)))
                     })
        .OrderByDescending(x => x.Similarity)
        .TakeWhile(x => x.Similarity > 0.5m) // <-- Threshold here!
        .Select(x => x.Suggestion);
}
// I'll be using anonymous types here, but you don't have to be lazy about it
var src = new {A = "Hello", B = "World", C = "and welcome!"};
var possibleSuggestions =
    new[]
    {
        new {A = "Hola", B = "World", C = "Welcome!"},
        new {A = "Bye", B = "world", C = "and fairwell"},
        new {A = "Hell", B = "World", C = "arrives..."},
        new {A = "Hello", B = "Earth", C = "and welcome!"},
        new {A = "Hi", B = "world", C = "welcome!"}
    };

var suggestions =
    Suggest(possibleSuggestions,
            x => new SuggestionField
                 {
                     SourceData = src.A,
                     SuggestedData = x.A,
                     Importance = 0.3m // 30%
                 },
            x => new SuggestionField
                 {
                     SourceData = src.B,
                     SuggestedData = x.B,
                     Importance = 0.5m // 50%
                 },
            x => new SuggestionField
                 {
                     SourceData = src.C,
                     SuggestedData = x.C,
                     Importance = 0.2m // 20%
                 }).ToArray();