我可以使用python're'来解析复杂的人名吗?

时间:2015-02-25 20:20:53

标签: python regex parsing string-parsing

因此,我的一个主要难点是名称理解和拼凑家喻户晓的名字。标题。我有一个80%的解决方案,我今天早上把一个非常庞大的正则表达式放在一起,我可能不应该为此感到自豪(但无论如何都是以一种恶心的方式),这些正确匹配以下示例:

John Jeffries
John Jeffries, M.D.
John Jeffries, MD
John Jeffries and Jim Smith
John and Jim Jeffries
John Jeffries & Jennifer Wilkes-Smith, DDS, MD
John Jeffries, CPA & Jennifer Wilkes-Smith, DDS, MD
John Jeffries, C.P.A & Jennifer Wilkes-Smith, DDS, MD
John Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD
John Jeffries M.D. and Jennifer Holmes CPA
John Jeffries M.D. & Jennifer Holmes CPA

正则表达式匹配器看起来像这样:

(?P<first_name>\S*\s*)?(?!and\s|&\s)(?P<last_name>[\w-]*\s*)(?P<titles1>,?\s*(?!and\s|&\s)[\w\.]*,*\s*(?!and\s|&\s)[\w\.]*)?(?P<connector>\sand\s|\s*&*\s*)?(?!and\s|&\s)(?P<first_name2>\S*\s*)(?P<last_name2>[\w-]*\s*)?(?P<titles2>,?\s*[\w\.]*,*\s*[\w\.]*)?

(wtf对吧?)

为方便起见:http://www.pyregex.com/

所以,例如:

'John Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD'

正则表达式导致组dict看起来像:

connector: &
first_name: John
first_name2: Jennifer
last_name: Jeffries
last_name2: Wilkes-Smith
titles1: , C.P.A., MD
titles2: , DDS, MD

我需要帮助我最后一步绊倒我,理解可能的中间名。

示例包括:

'John Jimmy Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD'
'John Jeffries, C.P.A., MD & Jennifer Jenny Wilkes-Smith, DDS, MD'

这是否可行,如果没有机器学习,有更好的方法吗?也许我可以使用nameparser(在我走下正则表达式兔子洞后发现)而不是用某种方法来确定是否有多个名字?以上匹配99.9%的案例,所以我觉得值得完成。

  

TLDR :我无法弄清楚我是否可以使用某种前瞻或后瞻来确保可能的中间名仅匹配   在它之后有一个姓氏。

注意:我不需要像Mrs. Mrs. Ms.等那样解析标题,但我想这可以像中间名一样添加。

  

解决方案说明:首先,请遵循理查德的建议,不要这样做。其次,如有必要,调查NLTK或使用/贡献nameparser以获得更强大的解决方案。

1 个答案:

答案 0 :(得分:8)

像这样的正则表达式是Dark One的作品。

以后查看您的代码的人将能够了解发生了什么?你愿意吗?

您将如何测试所有可能的边缘情况?

为什么你选择使用正则表达式?如果您使用的工具难以使用,则表明可能另一种工具会更好。

试试这个:

import re

examples = [
  "John Jeffries",
  "John Jeffries, M.D.",
  "John Jeffries, MD",
  "John Jeffries and Jim Smith",
  "John and Jim Jeffries",
  "John Jeffries & Jennifer Wilkes-Smith, DDS, MD",
  "John Jeffries, CPA & Jennifer Wilkes-Smith, DDS, MD",
  "John Jeffries, C.P.A & Jennifer Wilkes-Smith, DDS, MD",
  "John Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD",
  "John Jeffries M.D. and Jennifer Holmes CPA",
  "John Jeffries M.D. & Jennifer Holmes CPA",
  'John Jimmy Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD',
  'John Jeffries, C.P.A., MD & Jennifer Jenny Wilkes-Smith, DDS, MD'
]

def IsTitle(inp):
  return re.match('^([A-Z]\.?)+$',inp.strip())

def ParseName(name):
  #Titles are separated from each other and from names with ","
  #We don't need these, so we remove them
  name = name.replace(',',' ') 
  #Split name and titles on spaces, combining adjacent spaces
  name = name.split()
  #Build an output object
  ret_name = {"first":None, "middle":None, "last":None, "titles":[]}
  #First string is always a first name
  ret_name['first'] = name[0]
  if len(name)>2: #John Johnson Smith/PhD
    if IsTitle(name[2]): #John Smith PhD
      ret_name['last']   = name[1]
      ret_name['titles'] = name[2:]
    else:                #John Johnson Smith, PhD, MD
      ret_name['middle'] = name[1]
      ret_name['last']   = name[2]
      ret_name['titles'] = name[3:]
  elif len(name) == 2:   #John Johnson
    ret_name['last'] = name[1]
  return ret_name

def CombineNames(inp):
  if not inp[0]['last']:
    inp[0]['last'] = inp[1]['last']

def ParseString(inp):
  inp = inp.replace("&","and")    #Names are combined with "&" or "and"
  inp = re.split("\s+and\s+",inp) #Split names apart
  inp = map(ParseName,inp)
  CombineNames(inp)
  return inp

for e in examples:
  print e
  print ParseString(e)

输出:

John Jeffries
[{'middle': None, 'titles': [], 'last': 'Jeffries', 'first': 'John'}]
John Jeffries, M.D.
[{'middle': None, 'titles': ['M.D.'], 'last': 'Jeffries', 'first': 'John'}]
John Jeffries, MD
[{'middle': None, 'titles': ['MD'], 'last': 'Jeffries', 'first': 'John'}]
John Jeffries and Jim Smith
[{'middle': None, 'titles': [], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': [], 'last': 'Smith', 'first': 'Jim'}]
John and Jim Jeffries
[{'middle': None, 'titles': [], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': [], 'last': 'Jeffries', 'first': 'Jim'}]
John Jeffries & Jennifer Wilkes-Smith, DDS, MD
[{'middle': None, 'titles': [], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]
John Jeffries, CPA & Jennifer Wilkes-Smith, DDS, MD
[{'middle': None, 'titles': ['CPA'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]
John Jeffries, C.P.A & Jennifer Wilkes-Smith, DDS, MD
[{'middle': None, 'titles': ['C.P.A'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]
John Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD
[{'middle': None, 'titles': ['C.P.A.', 'MD'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]
John Jeffries M.D. and Jennifer Holmes CPA
[{'middle': None, 'titles': ['M.D.'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['CPA'], 'last': 'Holmes', 'first': 'Jennifer'}]
John Jeffries M.D. & Jennifer Holmes CPA
[{'middle': None, 'titles': ['M.D.'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['CPA'], 'last': 'Holmes', 'first': 'Jennifer'}]
John Jimmy Jeffries, C.P.A., MD & Jennifer Wilkes-Smith, DDS, MD
[{'middle': 'Jimmy', 'titles': ['C.P.A.', 'MD'], 'last': 'Jeffries', 'first': 'John'}, {'middle': None, 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]
John Jeffries, C.P.A., MD & Jennifer Jenny Wilkes-Smith, DDS, MD
[{'middle': None, 'titles': ['C.P.A.', 'MD'], 'last': 'Jeffries', 'first': 'John'}, {'middle': 'Jenny', 'titles': ['DDS', 'MD'], 'last': 'Wilkes-Smith', 'first': 'Jennifer'}]

这花了不到十五分钟,在每个阶段,逻辑清晰,程序可以分段调试。虽然单行很可爱,但清晰度和可测试性应该优先。