31

许多联系人管理程序都会这样做——你输入一个名字(例如,“John W. Smith”),它会自动在内部将其分解为:

名字: John
中间名: W.
姓氏: Smith

同样,它会计算出诸如“Jane W. Smith 夫人”和“Dr. John Doe, Jr.”之类的东西。也正确(假设您允许在名称中使用“前缀”和“后缀”等字段)。

我认为这是人们想要做的相当普遍的事情......所以问题是......你会怎么做?有一个简单的算法吗?也许是一个正则表达式?

我正在寻求 .NET 解决方案,但我并不挑剔。

更新:我很感激没有涵盖所有边缘案例和文化的简单解决方案......但是为了争论起见,我们假设您需要分片的名称(填写表格 - 例如税收或其他政府表格 - 是您必须在固定字段中输入姓名的一种情况,无论您喜欢与否),但您不一定要强制用户将他们的姓名输入离散字段(更少打字=更容易新手用户)。

您希望程序“猜测”(尽其所能)第一个、中间、最后一个等。如果可以,请查看 Microsoft Outlook 如何为联系人执行此操作 - 它允许您输入姓名,但是如果您需要澄清,可以打开一个额外的小窗口。我会做同样的事情——给用户一个窗口,以防他们想以离散的部分输入名称——但允许在一个框中输入名称并进行涵盖常见名称的“最佳猜测”。

4

23 回答 23

33

如果你必须做这个解析,我相信你会在这里得到很多好的建议。

我的建议是 -不要做这个解析

相反,创建您的输入字段,以便信息已经被分离出来。有标题、名字、中间名首字母、姓氏、后缀等单独的字段。

于 2008-09-19T16:29:19.577 回答
17

我知道这是旧的,可​​能是我已经找不到的地方的答案,但由于我找不到任何适合我的东西,这就是我想出的,我认为它很像 Google 联系人和 Microsoft Outlook。它不能很好地处理边缘情况,但是对于一个好的 CRM 类型的应用程序,总是可以要求用户解决这些问题(在我的应用程序中,我实际上一直都有单独的字段,但我需要从另一个应用程序导入数据只有一个字段):

    public static void ParseName(this string s, out string prefix, out string first, out string middle, out string last, out string suffix)
    {
        prefix = "";
        first = "";
        middle = "";
        last = "";
        suffix = "";

        // Split on period, commas or spaces, but don't remove from results.
        List<string> parts = Regex.Split(s, @"(?<=[., ])").ToList();

        // Remove any empty parts
        for (int x = parts.Count - 1; x >= 0; x--)
            if (parts[x].Trim() == "")
                parts.RemoveAt(x);

        if (parts.Count > 0)
        {
            // Might want to add more to this list
            string[] prefixes = { "mr", "mrs", "ms", "dr", "miss", "sir", "madam", "mayor", "president" };

            // If first part is a prefix, set prefix and remove part
            string normalizedPart = parts.First().Replace(".", "").Replace(",", "").Trim().ToLower();
            if (prefixes.Contains(normalizedPart))
            {
                prefix = parts[0].Trim();
                parts.RemoveAt(0);
            }
        }

        if (parts.Count > 0)
        {
            // Might want to add more to this list, or use code/regex for roman-numeral detection
            string[] suffixes = { "jr", "sr", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", "xi", "xii", "xiii", "xiv", "xv" };

            // If last part is a suffix, set suffix and remove part
            string normalizedPart = parts.Last().Replace(".", "").Replace(",", "").Trim().ToLower();
            if (suffixes.Contains(normalizedPart))
            {
                suffix = parts.Last().Replace(",", "").Trim();
                parts.RemoveAt(parts.Count - 1);
            }
        }

        // Done, if no more parts
        if (parts.Count == 0)
            return;

        // If only one part left...
        if (parts.Count == 1)
        {
            // If no prefix, assume first name, otherwise last
            // i.e.- "Dr Jones", "Ms Jones" -- likely to be last
            if(prefix == "")
                first = parts.First().Replace(",", "").Trim();
            else
                last = parts.First().Replace(",", "").Trim();
        }

        // If first part ends with a comma, assume format:
        //   Last, First [...First...]
        else if (parts.First().EndsWith(","))
        {
            last = parts.First().Replace(",", "").Trim();
            for (int x = 1; x < parts.Count; x++)
                first += parts[x].Replace(",", "").Trim() + " ";
            first = first.Trim();
        }

        // Otherwise assume format:
        // First [...Middle...] Last

        else
        {
            first = parts.First().Replace(",", "").Trim();
            last = parts.Last().Replace(",", "").Trim();
            for (int x = 1; x < parts.Count - 1; x++)
                middle += parts[x].Replace(",", "").Trim() + " ";
            middle = middle.Trim();
        }
    }

抱歉,代码又长又丑,我还没来得及清理它。它是一个 C# 扩展,因此您可以像这样使用它:

string name = "Miss Jessica Dark-Angel Alba";
string prefix, first, middle, last, suffix;
name.ParseName(out prefix, out first, out middle, out last, out suffix);
于 2013-04-26T17:20:05.613 回答
11

对此没有简单的解决方案。 名称构造因文化而异,即使在英语世界中,前缀和后缀也不一定是名称的一部分。

一种基本方法是在字符串的开头查找敬语(例如,“Hon. John Doe”)和结尾的数字或其他字符串(例如,“John Doe IV”、“John Doe Jr.”),但实际上你所能做的就是应用一套启发式方法并希望最好。

查找未处理名称的列表并针对它测试您的算法可能会很有用。不过,我不知道那里有任何预先包装好的东西。

于 2008-09-19T16:31:26.517 回答
5

你可能真的不需要做任何花哨的事情。像这样的东西应该工作。

    Name = Name.Trim();

    arrNames = Name.Split(' ');

    if (arrNames.Length > 0) {
        GivenName = arrNames[0];
    }
    if (arrNames.Length > 1) {
        FamilyName = arrNames[arrNames.Length - 1];
    }
    if (arrNames.Length > 2) {
        MiddleName = string.Join(" ", arrNames, 1, arrNames.Length - 2);
    }

您可能还想先检查标题。

于 2008-09-19T16:34:15.697 回答
4

我不得不这样做。实际上,比这更难的事情,因为有时“名字”会是“史密斯,约翰”或“史密斯约翰”而不是“约翰史密斯”,或者根本不是一个人的名字而是一个公司的名字。它必须自动完成,用户没有机会更正它。

我最终做的是想出一个名称可能包含的有限模式列表,例如:
Last, First Middle-Initial
First Last
First Middle-Initial Last
Last, First Middle
First Middle Last
First First Last

把你的先生,Jr的,也在那里。假设您最终得到了十几种模式。

我的应用程序有一个常见的名字、常见的姓氏(你可以在网上找到这些)、常见的标题、常见的后缀(jr、sr、md)的字典,使用它可以对模式做出真正好的猜测。我没那么聪明,我的逻辑也没那么花哨,但是,创建一些在 99% 以上的时间里猜对的逻辑并不难。

于 2008-09-20T02:58:13.437 回答
3

我明白这很难做到正确- 但是如果您为用户提供一种编辑结果的方法(例如,如果它没有猜对,则弹出窗口来编辑名称)并且仍然猜对大多数人案例......当然,这是很难的猜测。

从理论上看问题时说“不要这样做”很容易,但有时情况并非如此。包含名称所有部分的字段(标题、名字、中间名、姓氏、后缀,仅举几例)可能会占用大量屏幕空间 - 并结合地址问题(另一天的主题) 确实会弄乱应该是干净、简单的 UI。

我想答案应该是“除非你绝对必须这样做,否则不要这样做,如果你这样做,请保持简单(这里已经发布了一些方法)并为用户提供在需要时编辑结果的方法。”

于 2008-09-19T18:42:12.660 回答
3

理解这是一个坏主意,我用 perl 编写了这个正则表达式 - 这对我来说是最有效的。我已经过滤掉了公司名称。
以 vcard 格式输出:(hon_prefix、given_name、additional_name、family_name、hon.suffix)

/^ \s*
    (?:((?:Dr.)|(?:Mr.)|(?:Mr?s.)|(?:Miss)|(?:2nd\sLt.)|(?:Sen\.?))\s+)? # prefix
    ((?:\w+)|(?:\w\.)) # first name
(?: \s+ ((?:\w\.?)|(?:\w\w+)) )?  # middle initial
(?: \s+ ((?:[OD]['’]\s?)?[-\w]+))    # last name
(?: ,? \s+ ( (?:[JS]r\.?) | (?:Esq\.?) | (?: (?:M)|(?:Ph)|(?:Ed) \.?\s*D\.?) | 
         (?: R\.?N\.?) | (?: I+) )  )? # suffix
\s* $/x

笔记:

  • 不处理 IV、V、VI
  • 前缀、后缀的硬编码列表。从约 2K 名称的数据集演变而来
  • 不处理多个后缀(例如 MD、PhD)
  • 专为美国名字设计 - 不能在罗马化的日本名字或其他命名系统上正常工作
于 2008-12-26T21:43:02.933 回答
3

这里真正的解决方案没有回答这个问题。必须注意信息的预兆。名字不仅仅是一个名字;我们就是这样出名的。

这里的问题是不确切知道哪些部分标记了什么,以及它们的用途。尊敬的前缀只能在个人信件中使用;医生是由头衔派生的敬称。关于一个人的所有信息都与他们的身份相关,它只是确定什么是相关信息。出于管理原因,您需要名字和姓氏;电话号码、电子邮件地址、土地描述和邮寄地址;一切都是身份的象征,知道你在和谁打交道。

这里真正的问题是这个人在管理中迷失了方向。突然之间,他们只需将个人信息输入表格并提交给任意程序进行处理后,就可以得到预制模板喷出的各种敬语和取悦词。这是错误的;尊敬的先生或女士,如果对信函的原因表现出个人兴趣,那么绝对不应该使用模板来写信。个人信函需要对收件人有一点了解。男性或女性,上学当医生或法官,他们在什么文化中长大。

在其他文化中,名称由可变数量的字符组成。这个人的名字对我们来说只能解释为一串数字,其中空格实际上是由字符宽度而不是空格字符决定的。在这些情况下,敬语是一个或多个字符作为实际名称的前缀和后缀。礼貌的做法是使用给你的字符串,如果你知道敬语,那么一定要使用它,但这再次暗示了接受者的某种个人知识。除了 Sensei 以外的任何称呼 Sensei 都是错误的。不是逻辑错误,而是你刚刚侮辱了你的来电者,现在你应该找到一个可以帮助你道歉的模板。

出于自动化、非个人通信的目的,可以为每日文章、每周问题或其他内容设计模板,但当接收者向自动化服务发起通信时,问题就变得很重要。

发生的事情是一个错误。丢失的信息。未知或缺失的信息总是会产生异常。真正的问题不是你如何用表达式将一个人的名字分成不同的组成部分,而是你怎么称呼它们。

解决方案是创建一个额外的字段,如果已经有名字和姓氏,则将其设为可选,并将其称为“我们可以叫你什么”或“我们应该如何称呼你”。医生和法官将确保您正确解决这些问题。这些不是编程问题,而是沟通问题。

好的,不好的说法,但在我看来,用户名、标记名和 ID 更糟糕。所以我的解决方案;是缺少的问题,“我们应该怎么称呼你?”

这只是一个您有能力提出新问题的解决方案。机智占上风。在您的用户表单上创建一个新字段,将其命名为 Alias,为用户添加标签“我们应该怎么称呼您?”,然后您就有了与之交流的方式。使用名字和姓氏,除非收件人提供了别名,或者与发件人个人熟悉,则可以接受名字和中间名。

To Me, _______________________ (standard subscribed corrospondence)
To Me ( Myself | I ), ________ (standard recipient instigated corrospondence)
To Me Myself I, ______________ (look out, its your mother, and you're in big trouble;
                                nobody addresses a person by their actual full name)

Dear *(Mr./Mrs./Ms./Dr./Hon./Sen.) Me M. I *(I),
To Whom it may Concern;

否则,您正在寻找标准的东西:您好,问候,您可能是赢家。

如果您在一个字符串中包含一个人的姓名数据,那么您没有问题,因为您已经有了他们的别名。如果您需要的是名字和姓氏,那么只需 Left(name,instr(name," ")) & " " & Right(name,instrrev(name," ")),我的数学可能是错误的,我'我有点不习惯。将左右与已知的前缀和后缀进行比较,并从匹配中消除它们。除了确认身份的情况外,通常很少使用中间名;地址或电话号码可以告诉您更多信息。观察连字符,可以确定如果不使用姓氏,则将使用中间的一个。

为了搜索名字和姓氏的列表,必须考虑使用中间名字之一的可能性;这将需要四个搜索:一个过滤第一个和最后一个,然后另一个过滤第一个和中间,然后另一个过滤中间和最后一个,然后另一个过滤中间和中间。归根结底,第一个名字永远是第一个,最后一个永远是最后一个,并且可以有任意数量的中间名;少即是多,可能为零,但不太可能。

有时人们更喜欢被称为 Bill、Harry、Jim、Bob、Doug、Beth、Sue 或 Madonna;而不是他们的真实姓名;相似,但不切实际地期望任何人都能理解所有不同的可能性。

你能做的最有礼貌的事情就是问;我们可以叫你什么?

于 2012-03-01T00:11:32.480 回答
3

迟到了 10 年才开始这次谈话,但仍在寻找一个优雅的解决方案,我通读了这个帖子,并决定走 @eselk 所走的路,但要扩展它:

public class FullNameDTO
{
    public string Prefix     { get; set; }
    public string FirstName  { get; set; }
    public string MiddleName { get; set; }
    public string LastName   { get; set; }
    public string Suffix     { get; set; }
}

public static class FullName
{
    public static FullNameDTO GetFullNameDto(string fullName)
    {
        string[] knownPrefixes    = { "mr", "mrs", "ms", "miss", "dr", "sir", "madam", "master", "fr", "rev", "atty", "hon", "prof", "pres", "vp", "gov", "ofc" };
        string[] knownSuffixes    = { "jr", "sr", "ii", "iii", "iv", "v", "esq", "cpa", "dc", "dds", "vm", "jd", "md", "phd" };
        string[] lastNamePrefixes = { "da", "de", "del", "dos", "el", "la", "st", "van", "von" };

        var prefix     = string.Empty;
        var firstName  = string.Empty;
        var middleName = string.Empty;
        var lastName   = string.Empty;
        var suffix     = string.Empty;

        var fullNameDto = new FullNameDTO
        {
            Prefix     = prefix,
            FirstName  = firstName,
            MiddleName = middleName,
            LastName   = lastName,
            Suffix     = suffix
        };

        // Split on period, commas or spaces, but don't remove from results.
        var namePartsList = Regex.Split(fullName, "(?<=[., ])").ToList();

        #region Clean out the crap.
        for (var x = namePartsList.Count - 1; x >= 0; x--)
        {
            if (namePartsList[x].Trim() == string.Empty)
            {
                namePartsList.RemoveAt(x);
            }
        }
        #endregion

        #region Trim all of the parts in the list
        for (var x = namePartsList.Count - 1; x >= 0; x--)
        {
            namePartsList[x] = namePartsList[x].Trim();
        }
        #endregion

        #region Only one Name Part - assume a name like "Cher"
        if (namePartsList.Count == 1)
        {
            firstName = namePartsList.First().Replace(",", string.Empty).Trim();
            fullNameDto.FirstName = firstName;

            namePartsList.RemoveAt(0);
        }
        #endregion

        #region Get the Prefix
        if (namePartsList.Count > 0)
        {
            //If we find a prefix, save it and drop it from the overall parts
            var cleanedPart = namePartsList.First()
                                           .Replace(".", string.Empty)
                                           .Replace(",", string.Empty)
                                           .Trim()
                                           .ToLower();

            if (knownPrefixes.Contains(cleanedPart))
            {
                prefix = namePartsList[0].Trim();
                fullNameDto.Prefix = prefix;

                namePartsList.RemoveAt(0);
            }
        }
        #endregion

        #region Get the Suffix
        if (namePartsList.Count > 0)
        {
            #region Scan the full parts list for a potential Suffix
            foreach (var namePart in namePartsList)
            {
                var cleanedPart = namePart.Replace(",", string.Empty)
                                          .Trim()
                                          .ToLower();

                if (!knownSuffixes.Contains(cleanedPart.Replace(".", string.Empty))) { continue; }

                if (namePart.ToLower() == "jr" && namePart != namePartsList.Last()) { continue; }

                suffix             = namePart.Replace(",", string.Empty).Trim();
                fullNameDto.Suffix = suffix;

                namePartsList.Remove(namePart);
                break;
            }
            #endregion
        }
        #endregion

        //If, strangely, there's nothing else in the overall parts... we're done here.
        if (namePartsList.Count == 0) { return fullNameDto; }

        #region Prefix/Suffix taken care of - only one "part" left.
        if (namePartsList.Count == 1)
        {
            //If no prefix, assume first name (e.g. "Cher"), otherwise last (e.g. "Dr Jones", "Ms Jones")
            if (prefix == string.Empty)
            {
                firstName = namePartsList.First().Replace(",", string.Empty).Trim();
                fullNameDto.FirstName = firstName;
            }
            else
            {
                lastName = namePartsList.First().Replace(",", string.Empty).Trim();
                fullNameDto.LastName = lastName;
            }
        }
        #endregion

        #region First part ends with a comma
        else if (namePartsList.First().EndsWith(",") || (namePartsList.Count >= 3 && namePartsList.Any(n => n == ",") && namePartsList.Last() != ","))
        {
            #region Assume format: "Last, First"
            if (namePartsList.First().EndsWith(","))
            {
                lastName             = namePartsList.First().Replace(",", string.Empty).Trim();
                fullNameDto.LastName = lastName;
                namePartsList.Remove(namePartsList.First());

                firstName             = namePartsList.First();
                fullNameDto.FirstName = firstName;
                namePartsList.Remove(namePartsList.First());

                if (!namePartsList.Any()) { return fullNameDto; }

                foreach (var namePart in namePartsList)
                {
                    middleName += namePart.Trim() + " ";
                }
                fullNameDto.MiddleName = middleName;

                return fullNameDto;
            }
            #endregion

            #region Assume strange scenario like "Last Suffix, First"
            var indexOfComma = namePartsList.IndexOf(",");

            #region Last Name is the first thing in the list
            if (indexOfComma == 1)
            {
                namePartsList.Remove(namePartsList[indexOfComma]);

                lastName             = namePartsList.First().Replace(",", string.Empty).Trim();
                fullNameDto.LastName = lastName;
                namePartsList.Remove(namePartsList.First());

                firstName             = namePartsList.First();
                fullNameDto.FirstName = firstName;
                namePartsList.Remove(namePartsList.First());

                if (!namePartsList.Any()) { return fullNameDto; }

                foreach (var namePart in namePartsList)
                {
                    middleName += namePart.Trim() + " ";
                }
                fullNameDto.MiddleName = middleName;

                return fullNameDto;
            }
            #endregion

            #region Last Name might be a prefixed one, like "da Vinci"
            if (indexOfComma == 2)
            {
                var possibleLastPrefix = namePartsList.First()
                                                      .Replace(".", string.Empty)
                                                      .Replace(",", string.Empty)
                                                      .Trim()
                                                      .ToLower();

                if (lastNamePrefixes.Contains(possibleLastPrefix))
                {
                    namePartsList.Remove(namePartsList[indexOfComma]);

                    var lastPrefix = namePartsList.First().Trim();
                    namePartsList.Remove(lastPrefix);

                    lastName             = $"{lastPrefix} {namePartsList.First().Replace(",", string.Empty).Trim()}";
                    fullNameDto.LastName = lastName;
                    namePartsList.Remove(namePartsList.First());
                }
                else
                {
                    lastName = namePartsList.First().Replace(",", string.Empty).Trim();
                    namePartsList.Remove(namePartsList.First());

                    lastName = lastName + " " + namePartsList.First().Replace(",", string.Empty).Trim();
                    namePartsList.Remove(namePartsList.First());

                    fullNameDto.LastName = lastName;
                }

                namePartsList.Remove(",");

                firstName             = namePartsList.First();
                fullNameDto.FirstName = firstName;
                namePartsList.Remove(namePartsList.First());

                if (!namePartsList.Any()) { return fullNameDto; }

                foreach (var namePart in namePartsList)
                {
                    middleName += namePart.Trim() + " ";
                }
                fullNameDto.MiddleName = middleName;

                return fullNameDto;
            }
            #endregion
            #endregion
        }
        #endregion

        #region Everything else
        else
        {
            if (namePartsList.Count >= 3)
            {
                firstName = namePartsList.First().Replace(",", string.Empty).Trim();
                fullNameDto.FirstName = firstName;
                namePartsList.RemoveAt(0);

                //Check for possible last name prefix

                var possibleLastPrefix = namePartsList[namePartsList.Count - 2]
                                               .Replace(".", string.Empty)
                                               .Replace(",", string.Empty)
                                               .Trim()
                                               .ToLower();

                if (lastNamePrefixes.Contains(possibleLastPrefix))
                {
                    lastName = $"{namePartsList[namePartsList.Count - 2].Trim()} {namePartsList[namePartsList.Count -1].Replace(",", string.Empty).Trim()}";
                    fullNameDto.LastName = lastName;

                    namePartsList.RemoveAt(namePartsList.Count - 1);
                    namePartsList.RemoveAt(namePartsList.Count - 1);
                }
                else
                {
                    lastName = namePartsList.Last().Replace(",", string.Empty).Trim();
                    fullNameDto.LastName = lastName;

                    namePartsList.RemoveAt(namePartsList.Count - 1);
                }

                middleName = string.Join(" ", namePartsList).Trim();
                fullNameDto.MiddleName = middleName;

                namePartsList.Clear();
            }
            else
            {
                if (namePartsList.Count == 1)
                {
                    lastName = namePartsList.First().Replace(",", string.Empty).Trim();
                    fullNameDto.LastName = lastName;

                    namePartsList.RemoveAt(0);
                }
                else
                {
                    var possibleLastPrefix = namePartsList.First()
                                             .Replace(".", string.Empty)
                                             .Replace(",", string.Empty)
                                             .Trim()
                                             .ToLower();

                    if (lastNamePrefixes.Contains(possibleLastPrefix))
                    {
                        lastName = $"{namePartsList.First().Replace(",", string.Empty).Trim()} {namePartsList.Last().Replace(",", string.Empty).Trim()}";
                        fullNameDto.LastName = lastName;

                        namePartsList.Clear();
                    }
                    else
                    {
                        firstName = namePartsList.First().Replace(",", string.Empty).Trim();
                        fullNameDto.FirstName = firstName;

                        namePartsList.RemoveAt(0);

                        lastName = namePartsList.Last().Replace(",", string.Empty).Trim();
                        fullNameDto.LastName = lastName;

                        namePartsList.Clear();
                    }
                }
            }
        }
        #endregion

        namePartsList.Clear();

        fullNameDto.Prefix     = prefix;
        fullNameDto.FirstName  = firstName;
        fullNameDto.MiddleName = middleName;
        fullNameDto.LastName   = lastName;
        fullNameDto.Suffix     = suffix;

        return fullNameDto;
    }
}

这将处理相当多的不同场景,并且我已经针对它编写了(迄今为止)超过 50 个不同的单元测试以确保。

再次向@eselk 表示支持,因为他的想法帮助我编写了他出色解决方案的扩展版本。而且,作为奖励,这也处理了一个名为“JR”的人的奇怪实例。

于 2018-12-11T21:16:16.810 回答
2

我们在公司中使用了一些插件来完成此任务。我最终创建了一种方法来为不同的客户在不同的导入中实际指定名称的格式。根据我的经验,有一家公司的工具非常物有所值,并且在处理这个问题时真的令人难以置信。它位于: http: //www.softwarecompany.com/并且效果很好。在不使用任何统计方法的情况下执行此操作的最有效方法是用逗号或空格分隔字符串,然后:1. 去掉标题和前缀 2. 去掉后缀 3,按照 (2 names = F & L, 3 个名称 = FML 或 LMF) 取决于 string() 的顺序。

于 2010-01-25T20:20:26.553 回答
1

你可以做一些显而易见的事情:寻找 Jr.、II、III 等作为后缀,Mr.、Mrs.、Dr. 等作为前缀并删除它们,然后第一个单词是名字,最后一个单词是最后一个名字,中间的一切都是中间名。除此之外,没有万无一失的解决方案。

一个完美的例子是 David Lee Roth(姓:Roth)和 Eddie Van Halen(姓:Van Halen)。如果 Ann Marie Smith 的名字是“Ann Marie”,则无法将其与中间名 Marie 的 Ann 区分开来。

于 2008-09-19T16:31:35.750 回答
1

如果您只需要这样做,请将猜测添加到 UI 作为可选选择。这样,您可以告诉用户您是如何解析名称的,并让他们从您提供的列表中选择不同的解析方式。

于 2008-09-20T22:49:58.767 回答
1

有一个名为 NetGender 的第三方工具可以很好地工作。我用它来解析大量格式错误且格式不可预测的名称。查看他们页面上的示例,您也可以下载并尝试。

http://www.softwarecompany.com/dotnet/netgender.htm

我根据对 420 万个名字的抽样得出了这些统计数据。Name Parts 表示由空格分隔的不同部分的数量。数据库中大多数名称的正确率都很高。正确性随着部分的增加而下降,但是>3 部分的名称很少,>4 的名称更少。这对我们的案例来说已经足够好了。该软件失败的地方是识别不知名的多部分姓氏,即使用逗号分隔也是如此。如果它能够破译这一点,那么所有数据的错误总数将不到 1%。

Name Parts | Correct | Percent of Names in DB
 2             100%       48%
 3              98%       42%
 4              70%        9%
 5              45%        0.25%
于 2011-09-01T20:31:30.623 回答
1

我已经在页面加载时执行此服务器端。编写了一个 Coldfusion CFC,它将两个参数传递给它 - 实际用户数据(名字、中间名、姓氏)和数据类型(名字、中间名、姓氏)。然后相应地例行检查连字符、撇号、空格和格式。前任。MacDonald、McMurray、O'Neill、Rodham-Clinton、Eric von Dutch、GW Bush、Jack Burton Jr.、Dr. Paul Okin、Chris di Santos。对于用户只有一个名字的情况,只需要名字字段,中间名和姓氏是可选的。

所有信息都以小写形式存储 - 除了前缀、后缀和自定义。这种格式化是在页面呈现时完成的,而不是在存储到数据库期间完成的。虽然在用户输入数据时有验证过滤。抱歉,无法发布代码。开始使用正则表达式,但对于所有场景都变得过于混乱和不可靠。使用标准逻辑块(if/else、switch/case),更易于阅读和调试。使每个输入/数据库字段分开!是的,这需要一些编码,但在你完成后,它应该占到 99% 的组合。到目前为止仅基于英文名称,没有国际化,那是另一个蜡球。

这里有一些事情需要考虑:

  • Hypens(例如 Rodham-Clinton,可能在第一个、中间或最后一个)
  • 撇号(例如 O'Neill,可以在第一个、中间或最后一个)
  • 空间
  • Mc 和 Mac(例如 McDonald、MacMurray,可能在第一个、中间或
    最后一个)
  • 名字:多个名字(例如 Joe Bob Briggs)
  • 姓氏:de,di,et,der,den,van,von,af 应为小写(前 Eric von Dander、Mary di Carlo)
  • 前缀:博士、教授等
  • 后缀:Jr.、Sr.、Esq.、II、III 等

当用户输入信息时,db 中的字段模式是这样的:

  • 前缀/标题(博士等,使用下拉菜单)
  • 前缀/标题自定义(用户可以输入自定义,例如 Capt. 使用文本字段)
  • 中间
  • 后缀(Jr.、III、Prof.、Ret. 等使用下拉菜单)
  • 后缀自定义(用户可以输入自定义,例如 CPA)

这是我用来将每个名称的第一个字母大写的正则表达式。我先运行它,然后根据规则遵循例程格式(它是 Coldfusion 格式,但你明白了):

<cfset var nameString = REReplace(LCase(nameString), "(^[[:alpha:]]|[[:blank:]][[:alpha:]])", "\U\1\E", "ALL")>

您也可以使用 JavaScript 和 CSS 在客户端执行此操作——甚至可能更容易——但我更喜欢在服务器端执行,因为我需要在页面加载客户端之前设置变量。

于 2011-11-16T07:11:48.430 回答
0

我会说从列表中删除称呼,然后按空格分隔,将 list.first() 作为名字, list.last() 作为姓氏,然后用空格连接其余部分并将其作为中间名。最重要的是显示您的结果并让用户修改它们!

于 2008-09-19T16:31:59.447 回答
0

当然,有一个简单的解决方案 - 用空格分割字符串,计算标记的数量,如果有 2 个,则将它们解释为 FIRST 和 LAST,如果有 3 个,则将其解释为 FIRST、MIDDLE 和 LAST。

问题是简单的解决方案不会是 100% 正确的解决方案 - 有人总是可以输入带有更多标记的名称,或者可以包含标题、带有空格的姓氏(这可能吗?)等。你可以想出一个大多数时候都适用于大多数名字的解决方案,但不是一个绝对的解决方案。

我会按照 Shad 的建议拆分输入字段。

于 2008-09-19T16:33:00.333 回答
0

你不想这样做,除非你只打算接触来自一种文化的人。

例如:

Guido van Rossum 的姓氏是 van Rossum。

宫崎骏的名字叫Hayao。

你能做的最成功的事情就是去掉常见的头衔和称呼,并尝试一些启发式方法。

即便如此,最简单的解决方案是只存储全名,或者分别询问给定和姓氏。

于 2008-09-19T16:35:28.160 回答
0

这是一个愚蠢的差事。太多的异常无法确定地执行此操作。如果您这样做是为了对列表进行预处理以供进一步审查,我认为少即是多。

  1. 去掉称呼、头衔和世代后缀(大的正则表达式,或几个小的正则表达式)
  2. 如果只有一个名字,它是“最后一个”。
  3. 如果只有两个名字先分开,最后。
  4. 如果三个标记和中间是初始的,首先拆分它们,中间,最后
  5. 其余的手动排序。

几乎可以保证任何进一步的处理都会产生更多的工作,因为您必须重新组合处理拆分的内容。

于 2008-09-19T16:53:38.553 回答
0

我同意,对此没有简单的解决方案。但我在 Microsoft KB 5.0 的 VB 文章中发现了一种糟糕的方法,这是这里讨论的大部分讨论的实际实现:http: //support.microsoft.com/kb/168799

像这样的东西可以在紧要关头使用。

于 2008-12-02T20:39:14.513 回答
0

没有 100% 的方法可以做到这一点。

您可以按空格拆分,并尝试理解您想要的所有名称,但是归根结底,有时您会弄错。如果这足够好,请在此处寻找任何可以让您拆分的答案。

但是有些人会有像“John Wayne Olson”这样的名字,其中“John Wayne”是名字,而其他人会有像“John Wayne Olson”这样的名字,其中“Wayne”是他们的中间名。该名称中没有任何内容可以告诉您以哪种方式解释它。

就是那样子。这是一个模拟世界。

我的规则很简单。

取最后一部分 --> 姓
如果还有多个部分,取最后一部分 --> 中间名
剩下的 --> 名字

但不要认为这将是 100% 准确的,任何其他硬编码解决方案也不会。您将需要能够让用户自己编辑此内容。

于 2008-12-26T22:34:17.913 回答
0

我做了类似的事情。我遇到的主要问题是当人们输入“Richard R. Smith, Jr.”之类的内容时。我在http://www.blackbeltcoder.com/Articles/strings/splitting-a-name-into-first-and-last-names上发布了我的代码。它在 C# 中,但可以很容易地转换为 VB。

于 2011-01-28T22:21:13.510 回答
0

更简单的方法:

安装HumanNameParserNuGet:

Install-Package HumanNameParser

并调用 Parse 扩展方法。

string name = "Mr Ali R Von Bayat III";

var result = name.Parse();

//result = new Name()
//{
//    Salutation = "Mr",
//    FirstName = "Ali",
//    MiddleInitials = "R",
//    LastName = "Von Bayat",
//    Suffix = "III"
//};
于 2020-06-17T03:41:00.527 回答
-2

我同意不这样做。Rick Van DenBoer 这个名字最终会以 Van 的中间名结尾,但它是姓氏的一部分。

于 2008-09-19T16:32:49.990 回答