【问题标题】:Splitting names that include "de", "da", etc. into first, middle, last, etc将包含“de”、“da”等的名称拆分为 first、middle、last 等
【发布时间】:2018-07-01 04:18:43
【问题描述】:

我想将巴西名字分成几部分。然而,下面有一些名称,其中"de""da"(和其他)不是单独的部分,它们总是与以下单词一起使用。所以正常的拆分是行不通的。

test1 = "Francisco da Sousa Rodrigues" #special split
test2 = "Emiliano Rodrigo Carrasco" #normal split
test3 = "Alberto de Francia" #special split
test4 = "Bruno Rezende" #normal split

我的预期输出是:

[Francisco, da Sousa, Rodrigues] #1
[Emiliano, Rodrigo, Carrasco] #2
[Alberto, de Francia] #3
[Bruno, Rezende] #4

对于特殊情况我尝试了这种模式:

PATTERN = re.compile(r"\s(?=[da, de, do, dos, das])")
re.split(PATTERN, test1) (...)

但输出不是我所期望的:

['Francisco', 'da Sousa Rodrigues'] #1
['Alberto', 'de Francia'] #3

知道如何解决吗?有没有办法在“正常”和“特殊”情况下只使用一种模式?

【问题讨论】:

  • @pawelty 好的 OP。 为什么要拆分名称?
  • 我想计算每个部分在 Firstname 中出现的频率以及在 Surname 中出现的频率。然后,根据我们的编辑指南,我会将它们分成第一/中间/姓氏字段。在 100% 的情况下它可能并不完美,但我们可以接受。
  • @pawelty 就正则表达式而言,我没有更好的解决方案,但为什么你不只是拆分所有内容然后遍历结果列表,搜索“de”并将其与列表中的下一项作为字符串?在概念上似乎更容易,但我猜你有一个性能原因,对吧?不要误解,不要质疑你的方法,恰恰相反——想了解别人在做什么

标签: python regex python-3.x


【解决方案1】:

名称是否总是以“规范”方式书写,即除了 da、de、do、...之外的每个部分都大写?

在这种情况下,您可以使用该事实:

>>> import re
>>> for t in (test1, test2, test3, test4):
... print(re.findall(r"(?:[a-z]+ )?[A-Z]\w+", t, re.UNICODE))
['Francisco', 'da Sousa', 'Rodrigues']
['Emiliano', 'Rodrigo', 'Carrasco']
['Alberto', 'de Francia']
['Bruno', 'Rezende']
>>>

做你想做的事情的“正确”方法(除了根本不做之外)将是消极的后视:在没有任何 da、de、do、 ... .可悲的是,这(AFAIK)是不可能的,因为re 要求后视具有相同的宽度。如果音节中没有名字end,你真的不能假设,你可以这样做:

PATTERN = re.compile(r"(?<! da| de| do|dos|das)\s")

您可能偶尔会或可能不会偶然发现不起作用的情况:如果第一个字母是重音字符(或文章,假设包含重音字符),它将不正确匹配。要解决此问题,您将无法使用外部库; regex.

您的新 findall 将如下所示:

regex.findall(r"(?:\p{Ll}+ )?\p{Lu}\w+", "Luiz Ângelo de Urzêda")

\p{Ll}any 小写字母,\p{Lu}any 大写字母。

【讨论】:

  • '..每个部分大写除了...'
  • 这里有很多好的答案,但我会使用这个。刚刚与团队确认所有“特殊”字样都将小写。谢谢
  • @L3viathan 当单词以 test5 = "Luiz Ângelo de Urzêda" 之类的奇怪字母开头时,我对这种方法有疑问。它完全跳过了第二个词。
  • @pawelty 我预见到了问题并在一小时前编辑了我的答案;你需要regex 模块。
  • 如果您只关心典型的巴西名字,这很好。如果您有一个名为“Kitty St John O'Connor”的巴西公民,它的效果就不太好。 (她实际上是爱尔兰人,是Norman St. John-Stevas 的母亲。)
【解决方案2】:

使用 python 的 regex 库中的 regex.split() 函数提供额外的功能:

安装:

pip install regex

用法:

import regex as re

test_names = ["Francisco da Sousa Rodrigues", "Emiliano Rodrigo Carrasco",
              "Alberto de Francia", "Bruno Rezende"]

for n in test_names:
    print(re.split(r'(?<!das?|de|dos?)\s+', n))

输出:

['Francisco', 'da Sousa', 'Rodrigues']
['Emiliano', 'Rodrigo', 'Carrasco']
['Alberto', 'de Francia']
['Bruno', 'Rezende']

  • (?&lt;!das?|de|dos?)\s+ - 向后看否定断言(?&lt;!...) 确保空格\s+ 前面没有特殊情况之一da|das|de|do|dos

https://pypi.python.org/pypi/regex/

【讨论】:

  • 我得到这个错误:错误:look-behind requires fixed-width pattern
  • @pawelty,我很抱歉,这是带有扩展 regex 库的解决方案,它提供了额外的功能。查看我的更新
【解决方案3】:

您可以在findall 中使用这个正则表达式和一个可选组:

(?:(?:da|de|do|dos|das)\s+)?\S+

在这里,我们将(?:da|de|do|dos|das) 和 1+ 空格放在此可选之后。

RegEx Demo

Code Demo

代码示例:

test1 = "Francisco da Sousa Rodrigues" #special split
test2 = "Emiliano Rodrigo Carrasco" #normal split
test3 = "Alberto de Francia" #special split
test4 = "Bruno Rezende" #normal split

PATTERN = re.compile(r'(?:(?:da|de|do|dos|das)\s+)?\S+')

>>> print re.findall(PATTERN, test1)
['Francisco', 'da Sousa', 'Rodrigues']

>>> print re.findall(PATTERN, test2)
['Emiliano', 'Rodrigo', 'Carrasco']

>>> print re.findall(PATTERN, test3)
['Alberto', 'de Francia']

>>> print re.findall(PATTERN, test4)
['Bruno', 'Rezende']

【讨论】:

  • 这是 test1 和 test2 的错误行为。最后一个单词不分开。
  • 抱歉搞砸了。我已经更新了答案以获得正确的输出。
【解决方案4】:

将da替换为da_并将de替换为de_后,可以逐步实现:

lst = ["Francisco da Sousa Rodrigues" , 
    "Emiliano Rodrigo Carrasco" , 
    "Alberto de Francia" , 
    "Bruno Rezende" ] 

# replace da with da_ and de with de_
lst = list(map(lambda x: x.replace(" da ", " da_"), lst) ) 
lst = list(map(lambda x: x.replace(" de ", " de_"), lst) ) 
# now split names and then convert back _ to space: 
lst = [ [k.replace("_", " ")
        for k in l.split()]
      for l in lst ]
print(lst)

输出:

[['Francisco', 'da Sousa', 'Rodrigues'], 
 ['Emiliano', 'Rodrigo', 'Carrasco'], 
 ['Alberto', 'de Francia'], 
 ['Bruno', 'Rezende']]

编辑:针对评论,如果存在“Fernanda Rezende”类型名称,则可以将" da " 替换为" da_"(上面的代码从之前的"da " 更改为"da_"

也可以定义一个简单的函数来更改列表的所有字符串,然后使用它:

def strlist_replace(slist, oristr, newstr):
    return [ s.replace(oristr, newstr)
             for s in slist ]

lst = strlist_replace(lst, " da ", " da_")
lst = strlist_replace(lst, " de ", " de_")

【讨论】:

  • "Fernanda Rezende" 失败
  • 我认为这可以通过检查.startswith("da")来改进
【解决方案5】:

发生这种情况是因为您以特殊模式拆分字符串。这确实会将字符串分成两部分。

您可以尝试进一步拆分第二部分,再次使用“”作为分隔符。请注意,如果有多个特殊分隔符实例,这将不起作用。

另一种方法是使用“”作为分隔符继续拆分,并使用以下名称连接每个特殊分隔符。例如:

[Francisco, da, Sousa, Rodrigues] # becomes...
[Francisco, da Sousa, Rodrigues]

【讨论】:

    【解决方案6】:

    你可以试试这样的吗?

    b_o_g=['da', 'de', 'do', 'dos', 'das']
    test1 = "Francisco da Sousa Rodrigues"
    test3= "Alberto de Francia"
    
    
    
    
    def _custom_split (bag_of_words,string_t):
        s_o_s = string_t.split()
        for _,__ in enumerate(s_o_s):
            if __ in bag_of_words:
                try:
                    s_o_s[_]="{} {}".format(s_o_s[_],s_o_s[_+1])
                    del s_o_s [ _ + 1]
    
                except IndexError:
                    pass
        return s_o_s
    
    print(_custom_split(b_o_g,test1))
    print(_custom_split(b_o_g,test3))
    

    输出:

    ['Francisco', 'da Sousa', 'Rodrigues']
    ['Alberto', 'de Francia']
    

    【讨论】:

      【解决方案7】:

      也许不是最好或优雅的方式,但这会奏效。为了确定,我还添加了 test5。

      special_chars = ['da', 'de', 'do', 'dos', 'das']
      
      test1 = "Francisco da Sousa Rodrigues" #special split
      test2 = "Emiliano Rodrigo Carrasco" #normal split
      test3 = "Alberto de Francia" #special split
      test4 = "Bruno Rezende" #normal split
      test5 = 'Francisco da Sousa de Rodrigues'
      
      def cut(test):
          t1 = test.split()
          for i in range(len(t1)):
              if t1[i] in special_chars:
                  t1[i+1] = t1[i] + ' ' + t1[i+1]
          for i in t1:
              if i in special_chars:
                  t1.remove(i)
          print(t1)
      
      cut(test1)
      cut(test2)
      cut(test3)
      cut(test4)
      cut(test5)
      

      结果是:

      ['Francisco', 'da Sousa', 'Rodrigues']
      ['Emiliano', 'Rodrigo', 'Carrasco']
      ['Alberto', 'de Francia']
      ['Bruno', 'Rezende']
      ['Francisco', 'da Sousa', 'de Rodrigues']
      

      【讨论】:

        【解决方案8】:

        应该指出,我们在这里谈论的是标题,而不是名字。

        这些几乎都翻译成“来自”或“来自”之类的东西,后面的部分通常指的是一个地方,它们起源于贵族的头衔。

        您试图将非名称放入名称上下文中,这使一切变得困难。

        尝试删除所有这些就像它不存在一样很奇怪。就像你取了一个名字,比如“来自纽约的史蒂夫”,然后试着去掉这个名字,让纽约成为“姓氏”。

        这些从来都不是姓氏,也不是像大多数人认为的姓氏一样。随着时间的推移,事情有点朝着那个方向漂移,试图让圆形钉子适合方孔。

        您可以在注册页面或其他内容中添加标题字段,并将其用于具有标题的人作为更优雅的解决方案。

        【讨论】:

          【解决方案9】:

          你的正则表达式应该改成

          PATTERN = re.compile(r"\s(?=[da, de, do, dos, das])(\S+\s*\s\s*\S+)")

          import re
          
          test1 = "Francisco da Sousa Rodrigues" #special split
          test3 = "Alberto de Francia" #special split
          
          PATTERN = re.compile(r"\s(?=[da, de, do, dos, das])(\S+\s*\s\s*\S+)")
          print re.split(PATTERN, test1)
          print re.split(PATTERN, test3)
          

          这适用于我提供以下输出,

          ['弗朗西斯科','达索萨','罗德里格斯'] ['Alberto', 'de Francia', '']

          【讨论】:

          • @ktsenuri [da, de, do, dos, das] 不会按照您的预期进行。它相当于[adeos, ],即匹配任何这些字符,包括逗号或空格。
          • @cpburnz 感谢您的了解,因此应将其更改为 PATTERN = re.compile(r"\s(?=[da | de| do| dos| das])(\S+\ s*\s\s*\S+)")
          • @ktsenuri 不,[...] 仅用于匹配字符集,而不是单词。你会想要(?=da |de |do |dos |das )
          猜你喜欢
          • 2011-01-22
          • 1970-01-01
          • 2021-11-11
          • 1970-01-01
          • 2017-12-07
          • 1970-01-01
          • 1970-01-01
          • 2016-01-15
          • 1970-01-01
          相关资源
          最近更新 更多