在PHP中拆分名字和姓氏的最佳方法

15
我被卡在一个名称字段上,通常它的格式是:

I am stuck with a NAME field, which typically is in the format:


FirstName LastName

然而,我也有偶尔出现的名称符合以下任意一种格式(包括前缀或后缀):

Mr. First Last
First Last Jr.

大家认为在PHP中将这些内容安全地拆分为名/姓变量的方法是什么?我无法想出一个能够始终正常工作的方法...


1
按字符串拆分。跳过包含句点的元素。第一个被接受的元素是名字;第二个是姓氏。或者您想保留前缀/后缀? - Zach Rattner
1
没有完美的解决方案,应该在最初的设计中就解决这个问题。名称可以是一个或多个单词,任意顺序,任何标点符号。 - user557846
3
为了防止问题发生,最好在输入传递到PHP脚本之前验证输入。事后处理并没有一种完美的方式来处理所有情况。 - Brian Roach
1
这是一个特别困难的问题,想象一下“文森特·梵高先生”在您的网站上注册,预期结果会是什么? - TJHeuvel
1
为了任何阅读此内容的人的利益,请在实施任何与名称有关的解决方案之前,阅读《程序员对名称的错误信念》(http://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/)。 - Simba
显示剩余4条评论
16个回答

24

正则表达式是处理这种情况的最佳方法。 尝试使用以下代码段 - 它会提取前缀、名字、姓氏和后缀:

$array = array(
    'FirstName LastName',
    'Mr. First Last',
    'First Last Jr.',
    'Shaqueal O’neal',
    'D’angelo Hall',
);

foreach ($array as $name)
{
    $results = array();
    echo $name;
    preg_match('#^(\w+\.)?\s*([\'\’\w]+)\s+([\'\’\w]+)\s*(\w+\.?)?$#', $name, $results);
print_r($results);
}

结果如下:

FirstName LastName
Array
(
    [0] => FirstName LastName
    [1] => 
    [2] => FirstName
    [3] => LastName
)
Mr. First Last
Array
(
    [0] => Mr. First Last
    [1] => Mr.
    [2] => First
    [3] => Last
)
First Last Jr.
Array
(
    [0] => First Last Jr.
    [1] => 
    [2] => First
    [3] => Last
    [4] => Jr.
)
shaqueal o’neal
Array
(
    [0] => shaqueal o’neal
    [1] => 
    [2] => shaqueal
    [3] => o’neal
)
d’angelo hall
Array
(
    [0] => d’angelo hall
    [1] => 
    [2] => d’angelo
    [3] => hall
)

在这个数组中,$array[0] 包含整个字符串。$array[2] 总是名字的第一部分,而 $array[3] 总是名字的最后一部分。 $array[1] 是前缀,$array[4](不总是设置)是后缀。 我还添加了代码来处理像 Shaqueal O’neal 和 D’angelo Hall 这样的名字,包括 ' 和 ’ 符号。


有很多情况下这种方法是不可行的 - 请参考我下面的回答,特别是在国际化方面。 - Still don't know everything
这是80/20法则的很好运用。只需注意那20%的情况,以防不妥。 - Jeff Davis
太棒了。感谢您发布这个,正如其他人所说,它并不完美,但通常足够好。 - Nathan Pitman
如果姓名像Jomon K J先生,它将无法工作,但如果姓名像Jomon Johnson先生,它将起作用。 - Jomon Johnson

19

已接受的答案对于除英语以外的语言或像 "Oscar de la Hoya" 这样的名称无效。

这是我做的一些事情,我认为它是 utf-8 安全的,并适用于所有这些情况,建立在已接受的答案的假设上,即前缀和后缀将有一个句点:

/**
 * splits single name string into salutation, first, last, suffix
 * 
 * @param string $name
 * @return array
 */
public static function doSplitName($name)
{
    $results = array();

    $r = explode(' ', $name);
    $size = count($r);

    //check first for period, assume salutation if so
    if (mb_strpos($r[0], '.') === false)
    {
        $results['salutation'] = '';
        $results['first'] = $r[0];
    }
    else
    {
        $results['salutation'] = $r[0];
        $results['first'] = $r[1];
    }

    //check last for period, assume suffix if so
    if (mb_strpos($r[$size - 1], '.') === false)
    {
        $results['suffix'] = '';
    }
    else
    {
        $results['suffix'] = $r[$size - 1];
    }

    //combine remains into last
    $start = ($results['salutation']) ? 2 : 1;
    $end = ($results['suffix']) ? $size - 2 : $size - 1;

    $last = '';
    for ($i = $start; $i <= $end; $i++)
    {
        $last .= ' '.$r[$i];
    }
    $results['last'] = trim($last);

    return $results;
}

这是 PHP 单元测试:

public function testDoSplitName()
{
    $array = array(
        'FirstName LastName',
        'Mr. First Last',
        'First Last Jr.',
        'Shaqueal O\'neal',
        'D’angelo Hall',
        'Václav Havel',
        'Oscar De La Hoya',
        'АБВГҐД ЂЃЕЀЁЄЖЗ', //cyrillic
        'דִּיש מַחֲזֹור', //yiddish
    );

    $assertions = array(
            array(
                    'salutation' => '',
                    'first' => 'FirstName',
                    'last' => 'LastName',
                    'suffix' => ''
                ),
            array(
                    'salutation' => 'Mr.',
                    'first' => 'First',
                    'last' => 'Last',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'First',
                    'last' => 'Last',
                    'suffix' => 'Jr.'
                ),
            array(
                    'salutation' => '',
                    'first' => 'Shaqueal',
                    'last' => 'O\'neal',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'D’angelo',
                    'last' => 'Hall',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'Václav',
                    'last' => 'Havel',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'Oscar',
                    'last' => 'De La Hoya',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'АБВГҐД',
                    'last' => 'ЂЃЕЀЁЄЖЗ',
                    'suffix' => ''
                ),
            array(
                    'salutation' => '',
                    'first' => 'דִּיש',
                    'last' => 'מַחֲזֹור',
                    'suffix' => ''
                ),
        );

    foreach ($array as $key => $name)
    {
        $result = Customer::doSplitName($name);

        $this->assertEquals($assertions[$key], $result);
    }
}

如果用户输入了“名 中间 姓”(First Middle Last),但是输出需要将“中间”与“姓”分开,那该怎么办呢? - Joe Frambach

6
您不会找到一种安全的方法来解决这个问题,即使是人类有时也无法确定哪些部分属于姓和名,特别是当其中一个包含多个单词,比如 Andrea Frank Gutenberg。中间部分 Frank 可以是第二个名字,也可以是带有娘家姓的姓氏 Gutenberg
最好的办法是为姓和名提供不同的输入字段,并将它们分开保存在数据库中,这样可以避免很多问题。

2
作为一个学究,这假设人们的名字有两个部分,但并非总是如此。http://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ - dsas
@dsas - 这只是一个示例,展示了可能存在的一个问题。当然,并不是每个人都有几个名字,但如果你正在编写一款应该能够处理所有可能性的软件,这并没有什么帮助。 - martinstoeckli

4

如果你想简单地按以下方式分割名称:

  • 使用第一个“空格”字符之前的所有内容作为$firstName(名字)
  • 使用第一个“空格”字符之后的所有内容作为$lastName(姓氏)

你可以使用以下代码:

$firstName = substr($string, 0, strpos($string, ' '));
$lastName = substr($string, strlen($firstName));

这可能不是最复杂或文化敏感的方法,但只需两行代码,通常可以在不需要高度精确名称拆分的项目上完成工作。


1
这绝对不是其他解决方案中最优雅的解决方案,但它非常简单和有效 - 在某些情况下。我正在寻找确切的这个(并不是我不能写它,只是我太懒了),因为有时您只想将1个字段分成2个字段。+1 - dev_masta

3

可能最近会更新https://github.com/theiconic/name-parser。 - Ryan

3
不要分割名字。 始终将人们的姓名存储完整;如果您想使用缩写,请添加“我们应该称呼你什么?”字段。
原因:您无法可靠地拆分名称。 不同的国家以不同的顺序放置其名称(例如,在法国,姓氏通常排在前面;在一些远东国家也是如此,但您无法使用语言来检测,因为那些国家的移民经常交换他们的姓名以避免混淆...但并非所有移民都这样做)。
有些国家根本没有预期的名称结构;例如,在俄罗斯和冰岛,人们仍然使用父名而不是姓氏。
即使在英语中,也有双连字符的姓氏,没有连字符;然后有Mac,Mc,De,de,Van,van和其他前缀词作为其名称的一部分的人。最好忽略这个问题并问更明智的问题。
如果您被迫拆分名称以进行信用卡处理等操作,我会选择简单的方法,例如在最后一个空格处拆分,而不是尝试聪明地正确拆分。更有可能的是,如果卡公司执行了拆分,它将使用这种天真的方法,并且目标是匹配其可能的行为。不过,确实要抱怨只允许拆分名称的界面。

2

这不是一个简单的问题,而且在很大程度上,您能否得到可行的解决方案取决于文化“规范”。

  1. First hive off any "honorifics" - using preg_replace eg.

     $normalized_name = preg_replace('/^(Mr\.*\sJustice|Mr\.*\s+|Mrs\.*\s+|Ms\.\s+|Dr\.*\s+|Justice|etc.)*(.*)$/is', '$2', trim($input_name));
    
  2. Next hive off any trailing suffixes

    $normalized_name = preg_replace('/^(.*)(Jr\.*|III|Phd\.*|Md\.)$/is', '$1', $normalized_name);
    
  3. Finally split at the first blank to get a first name and last name.

显然,仅在英语中就有许多可能的敬语,我想不出太多的后缀,但可能比我列出的更多。


2
有另外一种解决方案:
// First, just for safety make replacement '.' for '. '
$both = str_replace('.', '. ', $both);

// Now delete titles
$both = preg_replace('/[^ ]+\./', '', $both);

// Delete redundant spaces
$both = trim(str_replace('  ', ' ', $both));

// Explode
$split = explode(" ", $both, 2);
if( count($split) > 1 ) {
    list($name, $surname) = $split;
} else {
    $name = $split[0];
    $surname = '';
}

1

首先你要分离FIRST/LAST,然后连接前缀。

以上是一个例子:

Vicent van Gogh

firstname 是数组的第一个索引。 在 firstname 之后的是 lastname,所以你只需要获取数组剩余的索引。

之后,你就可以连接前缀/后缀了。

Mr. Vicent van Gogh
Vicent van Gogh jr.


1
"Jan Willem van Gogh"的姓氏是"Jan",而名字是"Willen van Gogh"? - Jimmy Knoot

0

我总是建议尽可能从用户那里获取尽可能多的独立数据,同时只需要必要的数据以使函数正常工作。使用这种方法可以允许多种格式和名称构造方案。

在最终用户级别上独立捕获以下字段,很可能会消除解析的需要,或者至少清除特殊字符或拆分名称的解析问题,例如...“St. John”,“de la Hoya”和“Jr. III”。

  • 称谓(例如先生、女士、博士等)
  • 名字(例如John、Mary-Catherine、Mary Lou等)
  • 中间名(例如Davis、Alysia-Anne、D'Marco等)
  • 姓氏(例如de la Hoya、Smith-Peters、St. John等)
  • 后缀(例如Sr.、Jr.、Jr. III等)

一旦捕获了这些名称,程序员或最终用户(由程序员提供选项)可以根据需要动态重新排列、构造或格式化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接