如何计算.net中Metaphone / Soundex名称搜索的分数

时间:2014-03-24 06:45:20

标签: c# .net

我必须在soundex或Meta-phone匹配的搜索中获得名称字段的分数。例如:如果我搜索了#34; JOHN DOE"我把所有的声音都匹配在这个搜索参数上。它将返回类似于其soundex或Meta-phone匹配的大量记录。 因此,我需要根据获得的数据提供分数,以便可以在列表顶部获取或显示最匹配的数据。类似的用户可以从列表中获取85%或90%的匹配数据。 请帮助使用技巧在c#中为soundex或Meta-phone获得的值创建分数

3 个答案:

答案 0 :(得分:2)

我假设您搜索所有字符串并过滤掉查询字符串中包含所有soundex代码的字符串。因此,例如,如果查询是“John Doe”,那么您将有两个soundex代码,一个用于John,另一个用于DOE。接下来,您将检索至少包含这两个soundex代码的所有字符串。

现在,如果您获得了太多记录,那么您需要应用Information Retrieval域中的技术来对结果进行排名。不幸的是,有许多方法可以做到这一点。我将描述一些我最喜欢的方式来增加复杂性:

  1. 使用编辑距离对字符串进行排名。您将拥有函数GetEditDistance(s1,s2),它基本上返回您需要在s1中执行的添加/更新/删除次数以获取s2。这非常简单,您可以从此处获取代码和更多信息:How to calculate distance similarity measure of given 2 strings?
  2. 使用相似度量,例如Jaccard相似度。你基本上取两个字符串,得到普通字符数除以所有不同字符数的比例。这是角色等级的Jaccard分数。你也可以做令牌级别。例如,“John Doe”和“John Wolfenstein”之间的令牌级Jaccard得分是1/3,但对于“John Doe”和“John F. Doe”,得分是2/3。其他相似性指标是Dice和Cosine,它们也很容易计算并且专门用于维基百科页面。
  3. 最后,如果你想在上面链接的IR书中“正确”地进行,那么你需要先计算TF/IDF。这基本上为您记录中的每个术语赋予权重。如果术语发生的次数太多(如约翰),则其权重会降低。如果术语相当罕见(如Wolfenstein)则其重量更高。一旦你有权重,你基本上使用我在#2中描述的相似性度量。
  4. 更新了例如OP的评论

    在你的例子中,查询是osama,结果是osama,ossama,ussama,oswin,ASAMOAH。 在我看来,在你的情况下,骰子系数或余弦相似性最好。计算骰子系数非常容易,所以我会在这里使用它,但你也可能想要尝试余弦相似性。

    要计算角色等级骰子系数,请使用以下公式:

    Dice coefficient = 2 * (count of common characters between query and result) / (sum of all characters in query and result)
    

    例如,osama和ossama之间的骰子系数为2*5/(5+6)=0.91

    以下是查询osama的所有结果的骰子:

    osama   osama   ->  1.00
    osama   ossama  ->  0.91
    osama   ussama  ->  0.72
    osama   oswin   ->  0.40
    osama   ASAMOAH ->  0.83
    

    所以排名结果将是osama,ossama,ASAMOAH,ussama,oswin,这对我来说是合理的。

答案 1 :(得分:0)

我不完全确定你在这里问的是什么,但是我在这里解释了我的答案:

  

鉴于两个soundex或metaphone值,我如何评估哪一个更接近特定值?

(如果那不是您要求的,请随意忽略此帖的其余部分。)

几年前,我决定想知道拼写检查员的工作方式。所以,我看着aspell之类的东西,结果发现他们做了几乎与你想做的事情相同的事情。说你有价值:TELEPHON。您需要在字典中找到与其匹配的最接近的单词。你通过某种语音算法运行它,你得到TELEPHON的语音表示,可能是TLPN。 (语音算法很奇怪。)然后你浏览你的键值对列表(值是你字典中的一个单词;键是通过相同的语音算法运行的单词),你找到最接近TLPN的单词。那些是你的候选人。

比这更困难,但这是你想要做的事情,对吗?

如果是这种情况,那么您正在寻找的是距离算法。它们采用两个字符串来衡量将一个字符串转换为另一个字符串所需的编辑次数,它提供了一种比较两个字符串的简单机制。

我使用了Levenshtein Distance Algorithm。它很简单,很容易理解。您可以对其进行一些不同的修改,但如果您使用语音算法作为输入,则可能不需要。

所以是的,看看那个。 (请原谅我冗长的答案。)

(补充说明:如果您认为语音算法很奇怪,您应该看到字符串距离算法。尝试手动运行一次 - 您最终得到的矩阵似乎是一个随机混乱的数字..但是矩阵右下角的那个是正确的答案,不知何故。)

答案 2 :(得分:0)

这是一个用C编写并在http://aspell.net/metaphone/metaphone-kuhn.txt找到的metaphone 1算法的非常懒惰的C#转换。单元测试包括在内并成功通过。

public class Metaphone
{
    const string VOWELS = "AEIOU";
    const string FRONTV = "EIY";   /* special cases for letters in FRONT of these */
    const string VARSON = "CSPTG"; /* variable sound--those modified by adding an "h"    */
    const string DOUBLE = ".";     /* let these double letters through */

    const char NULLCHAR = '\0';

    private int strchr(string s, char c)
    {
        if (s.IndexOf(c) < 0)
            return NULLCHAR;
        else
            return 1; //dummy value to indicate found because we don't use non NULL return
    }

    private void strncat(ref string s, string s2, int unusedLen)
    {
        s = s + s2;
    }

    private void strncat(ref string s, char c, int unusedLen)
    {
        s = s + new string(c, 1);
    }

    private int strlen(char[] s)
    {
        var i = 0;
        foreach (var c in s)
        {
            if (c == NULLCHAR)
                return i;
            i++;
        }
        return -1;
    }

    private int strlen(string s)
    {
        return s.Length;
    }

    private void ShiftLeftByOne(char[] chars, int firstDestIndex)
    {
        for (var i = firstDestIndex; i < chars.Length - 1; i++)
            chars[i] = chars[i + 1];
    }

    private bool startsWith(char[] chars, char c1, char c2)
    {
        return chars[0] == c1 && chars[1] == c2;
    }

    public string GetPhonetic(string name, int maxLen)
    {
        string metaph = "";
        int ii, jj, Lng, lastChr;
        bool silent, hard;

        char curLtr, prevLtr, nextLtr, nextLtr2, nextLtr3;

        bool vowelAfter, vowelBefore, frontvAfter;

        char[] wname = new char[60];
        char[] ename = wname;

        jj = 0;
        for (ii = 0; ii < name.Length; ii++)
        {
            if (char.IsLetter(name[ii]))
            {
                ename[jj] = char.ToUpper(name[ii]);
                jj++;
            }
        }
        ename[jj] = NULLCHAR;

        if (strlen(ename) == 0)
            return null;

        /* if ae, gn, kn, pn, wr then drop the first letter */
        //char *chrptr, *chrptr1;
        //if ((chrptr = strchr2(excpPAIR, ename[0])) != NULLCHAR)
        //{
        //    chrptr1 = nextLTR + (chrptr - excpPAIR);
        //    if (*chrptr1 == ename[1])
        if (
            startsWith(ename, 'A', 'E') ||
            startsWith(ename, 'G', 'N') ||
            startsWith(ename, 'K', 'N') ||
            startsWith(ename, 'P', 'N') ||
            startsWith(ename, 'W', 'R')
            )
            ShiftLeftByOne(ename, 0);

        /* change x to s */
        if (ename[0] == 'X')
            ename[0] = 'S';

        /* get rid of the "h" in "wh" */
        //if (strncmp(ename, "WH", 2) == 0)
        if (startsWith(ename, 'W', 'H'))
            ShiftLeftByOne(ename, 1);

        Lng = strlen(ename);
        lastChr = Lng - 1;   /* index to last character in string makes code easier*/

        /* Remove an S from the end of the string */
        if (ename[lastChr] == 'S')
        {
            ename[lastChr] = NULLCHAR;
            Lng = strlen(ename);
            lastChr = Lng - 1;
        }

        for (ii = 0; ((strlen(metaph) < maxLen) && (ii < Lng)); ii++)
        {
            curLtr = ename[ii];

            vowelBefore = false;
            prevLtr = ' ';
            if (ii > 0)
            {
                prevLtr = ename[ii - 1];
                if (strchr(VOWELS, prevLtr) != NULLCHAR)
                    vowelBefore = true;
            }
            /* if first letter is a vowel KEEP it */
            if (ii == 0 && (strchr(VOWELS, curLtr) != NULLCHAR))
            {
                strncat(ref metaph, curLtr, 1);
                continue;
            }

            vowelAfter = false;
            frontvAfter = false;
            nextLtr = ' ';
            if (ii < lastChr)
            {
                nextLtr = ename[ii + 1];
                if (strchr(VOWELS, nextLtr) != NULLCHAR)
                    vowelAfter = true;
                if (strchr(FRONTV, nextLtr) != NULLCHAR)
                    frontvAfter = true;
            }
            /* skip double letters except ones in list */
            if (curLtr == nextLtr && (strchr(DOUBLE, nextLtr) == NULLCHAR))
                continue;

            nextLtr2 = ' ';
            if (ii < (lastChr - 1))
                nextLtr2 = ename[ii + 2];

            nextLtr3 = ' ';
            if (ii < (lastChr - 2))
                nextLtr3 = ename[ii + 3];

            switch (curLtr)
            {
                case 'B':
                    silent = false;
                    if (ii == lastChr && prevLtr == 'M')
                        silent = true;
                    if (!silent)
                        strncat(ref metaph, curLtr, 1);
                    break;

                /*silent -sci-,-sce-,-scy-;  sci-, etc OK*/
                case 'C':
                    if (!(ii > 1 && prevLtr == 'S' && frontvAfter))
                        if (ii > 0 && nextLtr == 'I' && nextLtr2 == 'A')
                            strncat(ref metaph, "X", 1);
                        else
                            if (frontvAfter)
                                strncat(ref metaph, "S", 1);
                            else
                                if (ii > 1 && prevLtr == 'S' && nextLtr == 'H')
                                    strncat(ref metaph, "K", 1);
                                else
                                    if (nextLtr == 'H')
                                        if (ii == 0 && (strchr(VOWELS, nextLtr2) == NULLCHAR))
                                            strncat(ref metaph, "K", 1);
                                        else
                                            strncat(ref metaph, "X", 1);
                                    else
                                        if (prevLtr == 'C')
                                            strncat(ref metaph, "C", 1);
                                        else
                                            strncat(ref metaph, "K", 1);
                    break;

                case 'D':
                    if (nextLtr == 'G' && (strchr(FRONTV, nextLtr2) != NULLCHAR))
                        strncat(ref metaph, "J", 1);
                    else
                        strncat(ref metaph, "T", 1);
                    break;

                case 'G':
                    silent = false;
                    /* SILENT -gh- except for -gh and no vowel after h */
                    if ((ii < (lastChr - 1) && nextLtr == 'H')
                        && (strchr(VOWELS, nextLtr2) == NULLCHAR))
                        silent = true;

                    if ((ii == (lastChr - 3))
                        && nextLtr == 'N' && nextLtr2 == 'E' && nextLtr3 == 'D')
                        silent = true;
                    else
                        if ((ii == (lastChr - 1)) && nextLtr == 'N')
                            silent = true;

                    if (prevLtr == 'D' && frontvAfter)
                        silent = true;

                    if (prevLtr == 'G')
                        hard = true;
                    else
                        hard = false;

                    if (!silent)
                        if (frontvAfter && (!hard))
                            strncat(ref metaph, "J", 1);
                        else
                            strncat(ref metaph, "K", 1);
                    break;

                case 'H':
                    silent = false;
                    if (strchr(VARSON, prevLtr) != NULLCHAR)
                        silent = true;

                    if (vowelBefore && !vowelAfter)
                        silent = true;

                    if (!silent)
                        strncat(ref metaph, curLtr, 1);
                    break;

                case 'F':
                case 'J':
                case 'L':
                case 'M':
                case 'N':
                case 'R':
                    strncat(ref metaph, curLtr, 1);
                    break;

                case 'K':
                    if (prevLtr != 'C')
                        strncat(ref metaph, curLtr, 1);
                    break;

                case 'P':
                    if (nextLtr == 'H')
                        strncat(ref metaph, "F", 1);
                    else
                        strncat(ref metaph, "P", 1);
                    break;

                case 'Q':
                    strncat(ref metaph, "K", 1);
                    break;

                case 'S':
                    if (ii > 1 && nextLtr == 'I'
                        && (nextLtr2 == 'O' || nextLtr2 == 'A'))
                        strncat(ref metaph, "X", 1);
                    else
                        if (nextLtr == 'H')
                            strncat(ref metaph, "X", 1);
                        else
                            strncat(ref metaph, "S", 1);
                    break;

                case 'T':
                    if (ii > 1 && nextLtr == 'I'
                        && (nextLtr2 == 'O' || nextLtr2 == 'A'))
                        strncat(ref metaph, "X", 1);
                    else
                        if (nextLtr == 'H')         /* The=0, Tho=T, Withrow=0 */
                            if (ii > 0 || (strchr(VOWELS, nextLtr2) != NULLCHAR))
                                strncat(ref metaph, "0", 1);
                            else
                                strncat(ref metaph, "T", 1);
                        else
                            if (!(ii < (lastChr - 2) && nextLtr == 'C' && nextLtr2 == 'H'))
                                strncat(ref metaph, "T", 1);
                    break;

                case 'V':
                    strncat(ref metaph, "F", 1);
                    break;

                case 'W':
                case 'Y':
                    if (ii < lastChr && vowelAfter)
                        strncat(ref metaph, curLtr, 1);
                    break;

                case 'X':
                    strncat(ref metaph, "KS", 2);
                    break;

                case 'Z':
                    strncat(ref metaph, "S", 1);
                    break;
            }
        }

        /*  
         DON'T DO THIS NOW, REMOVING "S" IN BEGINNING HAS the same effect
         with plurals, in addition imbedded S's in the Metaphone are included:

            Lng = strlen(metaph);
            lastChr = Lng -1;
            if ( metaph[lastChr] == 'S' && Lng >= 3 ) 
                metaph[lastChr] = '\0';
        */

        return metaph;
    }

    /*
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void Metaphone1()
        {
            var mp = new Metaphone();

            var pairs = new Dictionary<string, string>();
            pairs.Add("ANASTHA", "ANS0");
            pairs.Add("DAVIS-CARTER", "TFSKRTR");
            pairs.Add("ESCARMANT", "ESKRMNT ");
            pairs.Add("MCCALL", "MCL");
            pairs.Add("MCCROREY", "MCRR");
            pairs.Add("MERSEAL", "MRSL");
            pairs.Add("PIEURISSAINT", "PRSNT");
            pairs.Add("ROTMAN", "RTMN");
            pairs.Add("SCHEVEL", "SXFL");
            pairs.Add("SCHROM", "SXRM");
            pairs.Add("SEAL", "SL");
            pairs.Add("SPARR", "SPR");
            pairs.Add("STARLEPER", "STRLPR");
            pairs.Add("THRASH", "TRX");

            foreach (var pair in pairs)
            {
                var output = mp.GetPhonetic(pair.Key, 20);
                System.Diagnostics.Debug.WriteLine("{0} = {1} > {2}", pair.Key, pair.Value, output);
            }

        }
    }
    */
}
相关问题