【问题标题】:How to measure similarity of 2 strings aside from computing distance除了计算距离之外,如何测量 2 个字符串的相似度
【发布时间】:2013-07-18 21:49:10
【问题描述】:

我正在创建一个程序来检查单词是否是简化的单词(txt、msg 等),如果是简化的,它会找到正确的拼写,例如 txt=text、msg=message。我在 c# 中使用 NHunspell Suggest Method,它建议所有可能的结果。

问题是如果我输入“txt”,结果是文本、tat、tot 等。我不知道如何选择正确的单词。我使用了 Levenshtein Distance (C# - Compare String Similarity),但结果仍然为 1。

输入:txt 结果:text = 1,ext = 1 tit = 1

您能帮我了解一下简体字的意思或正确拼写吗? 示例:味精

【问题讨论】:

  • 似乎一种改进方法是检查每个结果是否至少包含输入的所有字符。
  • 我同意 Corak - 对于您给出的示例,3 个结果中的正确选项“text”、“ext”和“tit”是唯一包含所有字母的选项,以正确的顺序出现在输入中
  • 谢谢,我试试那个。
  • 您似乎正在尝试编写一个拼写检查器,它也会提出建议。这不是一项微不足道的任务。您是否在编程方面遇到问题,或者想出一个算法?
  • 我正在寻找一种算法来找到正确的单词以及如何对算法进行编程。

标签: c# forms algorithm


【解决方案1】:

我认为像 word 这样的程序用来纠正拼写的一种方法是使用 NLP(自然语言处理)技术来获取拼写错误上下文中使用的名词/形容词的顺序。然后将其与已知的句子结构进行比较他们可以估计 70% 的拼写错误是名词的可能性,并使用该信息来过滤更正的拼写。

SharpNLP 看起来像一个不错的库,但我还没有机会摆弄它。顺便说一句,为了建立一个已知句子结构的库,我们在 uni 中将我们的算法应用于公共领域的书籍。

查看我在 SO (download here, docs here) 上找到的 sams simMetrics library,了解除 Levenshtein 距离之外的更多算法选项。

【讨论】:

  • 我必须试试这个,我正在制作的程序真的是为了我的论文(推特上的情感)。我正在尝试检查推文是否有快捷词。谢谢。
  • 对不起,如果我的帖子令人困惑,它有助于我将其视为拼写纠正系统。只是一个想法,但有什么方法可以解析这些 wiki 页面以构建更全面的/专门为您服务的字典:en.wikipedia.org/wiki/List_of_acronyms:_A
【解决方案2】:

您确实需要实现 SQL 中存在的SOUNDEX 例程。我已经在下面的代码中做到了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Soundex
{
    class Program
    {
        static char[] ignoreChars = new char[] { 'a', 'e', 'h', 'i', 'o', 'u', 'w', 'y' };
        static Dictionary<char, int> charVals = new Dictionary<char, int>()
        {
            {'b',1},
            {'f',1},
            {'p',1},
            {'v',1},
            {'c',2},
            {'g',2},
            {'j',2},
            {'k',2},
            {'q',2},
            {'s',2},
            {'x',2},
            {'z',2},
            {'d',3},
            {'t',3},
            {'l',4},
            {'m',5},
            {'n',5},
            {'r',6}
        };

        static void Main(string[] args)
        {
            Console.WriteLine(Soundex("txt"));
            Console.WriteLine(Soundex("text"));
            Console.WriteLine(Soundex("ext"));
            Console.WriteLine(Soundex("tit"));
            Console.WriteLine(Soundex("Cammmppppbbbeeelll"));
        }

        static string Soundex(string s)
        {
            s = s.ToLower();
            StringBuilder sb = new StringBuilder();
            sb.Append(s.First());
            foreach (var c in s.Substring(1))
            {
                if (ignoreChars.Contains(c)) { continue; }

                // if the previous character yields the same integer then skip it
                if ((int)char.GetNumericValue(sb[sb.Length - 1]) == charVals[c]) { continue; }

                sb.Append(charVals[c]);
            }
            return string.Join("", sb.ToString().Take(4)).PadRight(4, '0');
        }
    }
}

看,使用此代码,您给出的示例中唯一匹配的是text。运行控制台应用程序,您将看到输出(即 txt 将匹配 text)。

【讨论】:

    【解决方案3】:

    我已经用您的示例数据测试了您的输入,只有 text 的距离为 25,而另一个距离为 33。这是我的代码:

    string input = "TXT";
    string[] words = new[]{"text","tat","tot"};
    var levenshtein = new Levenshtein();  
    const int maxDistance = 30;
    
    var distanceGroups = words
            .Select(w => new
            {
                Word = w,
                Distance = levenshtein.iLD(w.ToUpperInvariant(), input)
            })
            .Where(x => x.Distance <= maxDistance)
            .GroupBy(x => x.Distance)
            .OrderBy(g => g.Key)
            .ToList();
    foreach (var topCandidate in distanceGroups.First())
        Console.WriteLine("Word:{0} Distance:{1}", topCandidate.Word, topCandidate.Distance);
    

    这里是 levenshtein 类:

    public class Levenshtein
    {
        ///*****************************
        /// Compute Levenshtein distance 
        /// Memory efficient version
        ///*****************************
        public int iLD(String sRow, String sCol)
        {
            int RowLen = sRow.Length;  // length of sRow
            int ColLen = sCol.Length;  // length of sCol
            int RowIdx;                // iterates through sRow
            int ColIdx;                // iterates through sCol
            char Row_i;                // ith character of sRow
            char Col_j;                // jth character of sCol
            int cost;                   // cost
    
            /// Test string length
            if (Math.Max(sRow.Length, sCol.Length) > Math.Pow(2, 31))
                throw (new Exception("\nMaximum string length in Levenshtein.iLD is " + Math.Pow(2, 31) + ".\nYours is " + Math.Max(sRow.Length, sCol.Length) + "."));
    
            // Step 1
    
            if (RowLen == 0)
            {
                return ColLen;
            }
    
            if (ColLen == 0)
            {
                return RowLen;
            }
    
            /// Create the two vectors
            int[] v0 = new int[RowLen + 1];
            int[] v1 = new int[RowLen + 1];
            int[] vTmp;
    
    
    
            /// Step 2
            /// Initialize the first vector
            for (RowIdx = 1; RowIdx <= RowLen; RowIdx++)
            {
                v0[RowIdx] = RowIdx;
            }
    
            // Step 3
    
            /// Fore each column
            for (ColIdx = 1; ColIdx <= ColLen; ColIdx++)
            {
                /// Set the 0'th element to the column number
                v1[0] = ColIdx;
    
                Col_j = sCol[ColIdx - 1];
    
    
                // Step 4
    
                /// Fore each row
                for (RowIdx = 1; RowIdx <= RowLen; RowIdx++)
                {
                    Row_i = sRow[RowIdx - 1];
    
    
                    // Step 5
    
                    if (Row_i == Col_j)
                    {
                        cost = 0;
                    }
                    else
                    {
                        cost = 1;
                    }
    
                    // Step 6
    
                    /// Find minimum
                    int m_min = v0[RowIdx] + 1;
                    int b = v1[RowIdx - 1] + 1;
                    int c = v0[RowIdx - 1] + cost;
    
                    if (b < m_min)
                    {
                        m_min = b;
                    }
                    if (c < m_min)
                    {
                        m_min = c;
                    }
    
                    v1[RowIdx] = m_min;
                }
    
                /// Swap the vectors
                vTmp = v0;
                v0 = v1;
                v1 = vTmp;
    
            }
    
            // Step 7
    
            /// Value between 0 - 100
            /// 0==perfect match 100==totaly different
            /// 
            /// The vectors where swaped one last time at the end of the last loop,
            /// that is why the result is now in v0 rather than in v1
            //System.Console.WriteLine("iDist=" + v0[RowLen]);
            int max = System.Math.Max(RowLen, ColLen);
            return ((100 * v0[RowLen]) / max);
        }
    
    
        ///*****************************
        /// Compute the min
        ///*****************************
    
        private int Minimum(int a, int b, int c)
        {
            int mi = a;
    
            if (b < mi)
            {
                mi = b;
            }
            if (c < mi)
            {
                mi = c;
            }
    
            return mi;
        }
    
        ///*****************************
        /// Compute Levenshtein distance         
        ///*****************************
        public int LD(String sNew, String sOld)
        {
            int[,] matrix;              // matrix
            int sNewLen = sNew.Length;  // length of sNew
            int sOldLen = sOld.Length;  // length of sOld
            int sNewIdx; // iterates through sNew
            int sOldIdx; // iterates through sOld
            char sNew_i; // ith character of sNew
            char sOld_j; // jth character of sOld
            int cost; // cost
    
            /// Test string length
            if (Math.Max(sNew.Length, sOld.Length) > Math.Pow(2, 31))
                throw (new Exception("\nMaximum string length in Levenshtein.LD is " + Math.Pow(2, 31) + ".\nYours is " + Math.Max(sNew.Length, sOld.Length) + "."));
    
            // Step 1
    
            if (sNewLen == 0)
            {
                return sOldLen;
            }
    
            if (sOldLen == 0)
            {
                return sNewLen;
            }
    
            matrix = new int[sNewLen + 1, sOldLen + 1];
    
            // Step 2
    
            for (sNewIdx = 0; sNewIdx <= sNewLen; sNewIdx++)
            {
                matrix[sNewIdx, 0] = sNewIdx;
            }
    
            for (sOldIdx = 0; sOldIdx <= sOldLen; sOldIdx++)
            {
                matrix[0, sOldIdx] = sOldIdx;
            }
    
            // Step 3
    
            for (sNewIdx = 1; sNewIdx <= sNewLen; sNewIdx++)
            {
                sNew_i = sNew[sNewIdx - 1];
    
                // Step 4
    
                for (sOldIdx = 1; sOldIdx <= sOldLen; sOldIdx++)
                {
                    sOld_j = sOld[sOldIdx - 1];
    
                    // Step 5
    
                    if (sNew_i == sOld_j)
                    {
                        cost = 0;
                    }
                    else
                    {
                        cost = 1;
                    }
    
                    // Step 6
    
                    matrix[sNewIdx, sOldIdx] = Minimum(matrix[sNewIdx - 1, sOldIdx] + 1, matrix[sNewIdx, sOldIdx - 1] + 1, matrix[sNewIdx - 1, sOldIdx - 1] + cost);
    
                }
            }
    
            // Step 7
    
            /// Value between 0 - 100
            /// 0==perfect match 100==totaly different
            //System.Console.WriteLine("Dist=" + matrix[sNewLen, sOldLen]);
            int max = System.Math.Max(sNewLen, sOldLen);
            return (100 * matrix[sNewLen, sOldLen]) / max;
        }
    }
    

    【讨论】:

    • +1 非常优雅的实现。告诉我,我从 SQL 实现了SOUNDEX 算法。这两者有什么区别?我当然可以阅读代码,但我想从你那里得到一张高层次的图片,因为你清楚地理解了这个算法,并且可能理解了我实现的那个。例如,SOUNDEX 例程中是否有一些漏洞你知道吗?
    • 谢谢,但是输入的单词一定要大写吗?如果我将输入单词更改为小写,则错误是“序列不包含元素”。
    • @MMakati:那么开头的input.ToUpperInvariant()有什么问题呢?我必须承认我已经从我的代码中复制了这个,情况并不重要。我不知道这对你是否重要。 @Michael:实际上这个实现来自here。所以我不是 levenshtein 算法专家。
    • 我使用了 toUpper() 并且它可以工作,我在想这个案例是否会影响结果。但无论如何,这个答案很有帮助并且有效。谢谢@TimSchmelter。
    • @MMakati:当然情况会影响结果,这就是为什么我用ToUpper 来补偿这种影响。
    【解决方案4】:

    扩展我的评论,您可以使用正则表达式来搜索作为输入“扩展”的结果。像这样的:

    private int stringSimilarity(string input, string result)
    {
        string regexPattern = ""
        foreach (char c in input)
            regexPattern += input + ".*"
    
        Match match = Regex.Match(result, regexPattern,
        RegexOptions.IgnoreCase);
    
        if (match.Success)
            return 1;
        else
            return 0;
    }
    

    忽略 1 和 0 - 我不知道相似度评估的工作原理。

    【讨论】:

      【解决方案5】:

      不是一个完整的解决方案,只是一个希望有用的建议......

      在我看来,人们不太可能使用与正确单词一样长的简化,因此您至少可以过滤掉所有长度

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-03-05
        • 2012-07-25
        • 1970-01-01
        • 2011-08-27
        • 2014-12-23
        • 2016-03-06
        • 2011-10-01
        • 1970-01-01
        相关资源
        最近更新 更多