【问题标题】:REGEX: Remove spaces between strings with one or two letters正则表达式:删除带有一个或两个字母的字符串之间的空格
【发布时间】:2019-12-07 00:09:20
【问题描述】:

考虑下表第一列中显示的以下原始字符串:

Original String                  Parsed String                   Desired String
'W. & J. JOHNSON LMT.COM'       #W    J  JOHNSON LIMITED        #WJ JOHNSON LIMITED
'NORTH ROOF & WORKS CO. LTD.'   #NORTH ROOF   WORKS CO  LTD     #NORTH ROOF WORKS CO LTD
'DAVID DOE & CO., LIMITED'      #DAVID DOE   CO   LIMITED       #DAVID DOE CO LIMITED
'GEORGE TV & APPLIANCE LTD.'    #GEORGE TV   APPLIANCE LTD      #GEORGE TV APPLIANCE LTD 
'LOVE BROS. & OTHERS LTD.'      #LOVE BROS    OTHERS LTD        #LOVE BROS OTHERS LTD
'A. B. & MICHAEL CLEAN CO. LTD.'#A  B    MICHAEL CLEAN CO  LTD  #AB MICHAEL CLEAN CO LTD
'C.M. & B.B. CLEANER INC.'      #C M    B B  CLEANER INC        #CMBB CLEANER INC

需要删除标点符号,我已经这样做了:

def transform(word):
    word = re.sub(r'(?<=[A-Za-z])\'(?=[A-Za-z])[A-Z]|[^\w\s]|(.com|COM)',' ',word)

但是,我无法得到最后一点。删除标点符号后,我得到了很多空格。如何有一个正则表达式将首字母组合在一起并为常规单词保留单个空格(无首字母)?

这是用上述字符替换得到所需字符串的坏方法吗?

感谢您让我继续学习:)

【问题讨论】:

  • 我的错,它只是一个空格。所有单词都用一个空格分隔。
  • 看起来您正在用空格替换所有句点 . 和 & 符号 &amp;。我会分 3 个单独的步骤进行清理:删除特殊字符,用一个空格替换多个空格,然后删除每对单个字母之间的空格。
  • 那么从逻辑上讲,A. B. 如何变成ABC.M. &amp; B.B. 变成CM BB 而不是CMBB?但随后W. &amp; J. 变为WJ
  • . 替换为空,将&amp; 替换为空格是否有意义?
  • @ctwheels 是的,我检查了逻辑,它必须像你提到的那样。

标签: python regex etl regex-lookarounds


【解决方案1】:

我认为分段执行此操作更简单。首先,删除.com 和除space&amp; 之外的任何标点符号。然后,删除仅由一个字母包围的 space&amp;。最后,将剩余的space&amp; 序列替换为一个空格:

import re
strings = ['W. & J. JOHNSON LMT.COM',
'NORTH ROOF & WORKS CO. LTD.',
'DAVID DOE & CO., LIMITED',
'GEORGE TV & APPLIANCE LTD.',
'LOVE BROS. & OTHERS LTD.',
'A. B. & MICHAEL CLEAN CO. LTD.',
'C.M. & B.B. CLEANER INC.'
]

for s in strings:
    s = re.sub(r'\.COM|[^a-zA-Z& ]+', '', s, 0, re.IGNORECASE)
    s = re.sub(r'(?<=\b\w)\s*[ &]\s*(?=\w\b)', '', s)
    s = re.sub(r'\s*[& ]\s*', ' ', s)
    print s

输出

WJ JOHNSON LMT
NORTH ROOF WORKS CO LTD
DAVID DOE CO LIMITED
GEORGE TV APPLIANCE LTD
LOVE BROS OTHERS LTD
AB MICHAEL CLEAN CO LTD
CM BB CLEANER INC

Demo on rextester

更新

这是在对更改最后数据所需结果的问题进行编辑之前编写的。鉴于编辑,上面的代码可以简化为

for s in strings:
     s = re.sub(r'\.COM|[^a-zA-Z ]+|\s(?=&)|(?<!\w\w)\s+(?!\w\w)', '', s, 0, re.IGNORECASE)
     print s

Demo on rextester

【讨论】:

  • 感谢@Nick,我已经针对所有数据库运行了您的正则表达式,令人印象深刻的是如何达到以前的模式。执行时间为 15 秒。但是,我无法在正则表达式中包含一种模式,即两个单词之间的连字符:ALL-WEST GLASS。另一个不匹配的模式是数字和撇号,但我补充说:\.COM|[^a-zA-Z0-9 ]+|\s(?=&amp;)|(?&lt;!\w\w)\s+(?!\w\w)|''[A-Z]
  • @JuanPerez 对丢失的位感到抱歉 - 我只能处理您提供的数据。你解决了连字符的问题吗?当前的代码将删除它,您要保留它吗?
  • 我看到当前代码删除了连字符并将单词放在一起。我希望保留它并添加一个外部正则表达式来处理要被空格替换的连字符。
  • @JuanPerez 您可以将正则表达式稍微修改为r'\.COM|[^a-zA-Z -]+|\s(?=&amp;)|(?&lt;!\w\w)(?:\s+|-)(?!\w\w)',这将在单词之间保留连字符(然后用空格替换它)参见rextester.com/PKQ93755
  • 不确定为什么连字符在空格后面? [^a-zA-Z -]+
【解决方案2】:

仅在正则表达式中执行此操作并不漂亮,也不是最好的解决方案,但是,就在这里!你最好做一个多步骤的方法。我所做的是确定所有可能的情况,并选择找到没有替换字符串的解决方案,因为您并不总是用空格替换字符。


规则

  1. 非“堆叠”缩写
    • 这些位置类似于A. B.W. &amp; J.,但不是C.M. &amp; B.B.
    • 我已将这些位置标识为缩写部分(例如A.)前后存在的位置,但后者后面没有另一个字母字符
  2. 前面的空格
    • 这些位置在您的文本中不存在,但如果非字母字符前面有一个空格,后面没有空格(比如在行尾),则可能存在这些位置
    • 在这些情况下,我们匹配第一个空格之后的字符
  3. 进行空间
    • 这些位置类似于&amp;J. 中的点
    • 我们匹配这些示例中最后一个空格之前的字符
  4. 没有空格
    • 这些位置类似于'LOVE(该字符串中的撇号)
    • 我们只匹配非字母非空白字符

正则表达式

实现此目的的一体化正则表达式如下:

See regex in use here

(?<=\b[a-z])[^a-z]+(?=[a-z]\b(?![^a-z][a-z]))|(?<= ) *(?:\.com\b|[^a-z\s]+) *| *(?:\.com\b|[^a-z\s]+) *(?= )|(?<! )(?:\.com\b|[^a-z\s]+)(?! )

工作方式如下(分解为每个交替):

  • (?&lt;=\b[a-z])[^a-z]+(?=[a-z]\b(?![^a-z][a-z])) 匹配 A.B. 之间的非字母字符,但不匹配 A.B.B
    • (?&lt;=\b[a-z]) 正向向后看,确保前面是一个字母字符,并在其左侧声明一个单词边界位置
    • [^a-z]+ 匹配任何非字母字符一次或多次
    • (?=[a-z]\b(?![^a-z][a-z])) 正向前瞻确保以下内容存在
      • [a-z]\b 匹配任何字母字符并在其右侧声明一个单词边界位置
      • (?![^a-z][a-z]) 否定前瞻确保后面的不是非字母字符后跟一个字母字符
  • (?&lt;= ) *(?:\.com\b|[^a-z\s]+) * 确保前面有一个空格,然后匹配任何空格,.com 或任何非单词非空白字符一次或多次,然后匹配任何空格
    • (?&lt;= ) 正向向后看,确保前面有一个空格
    • * 匹配任意数量的空格
    • (?:\.com\b|[^a-z\s]+) 匹配 .com 并确保后面是非单词字符,或匹配任何非单词非空白字符一次或多次
    • * 匹配任意数量的空格
  • *(?:\.com\b|[^a-z\s]+) *(?= ) 匹配任何空格、.com 或任何非单词非空白字符一次或多次,然后匹配任何空格,然后确保后面有空格
    • 与上一个相同,但不是在开始时正面向后看,而是在结尾处正面向前看
  • (?&lt;! )(?:\.com\b|[^a-z\s]+)(?! ) 匹配 .com 或任何非字母非空白字符一次或多次,确保其周围没有空格
    • 与前两个选项相同,但使用负后瞻和负前瞻

代码

See code in use here

import re

strings = [
    "'W. & J. JOHNSON LMT.COM'",
    "'NORTH ROOF & WORKS CO. LTD.'",
    "'DAVID DOE & CO., LIMITED'",
    "'GEORGE TV & APPLIANCE LTD.'",
    "'LOVE BROS. & OTHERS LTD.'",
    "'A. B. & MICHAEL CLEAN CO. LTD.'",
    "'C.M. & B.B. CLEANER INC.'"
]

r = re.compile(r'(?<=\b[a-z])[^a-z]+(?=[a-z]\b(?![^a-z][a-z]))|(?<= ) *(?:\.com\b|[^a-z\s]+) *| *(?:\.com\b|[^a-z\s]+) *(?= )|(?<! )(?:\.com\b|[^a-z\s]+)(?! )', re.IGNORECASE)

def transform(word):
    return re.sub(r, '', word)

for s in strings:
    print(transform(s))

输出:

WJ JOHNSON LMT
NORTH ROOF WORKS CO LTD
DAVID DOE CO LIMITED
GEORGE TV APPLIANCE LTD
LOVE BROS OTHERS LTD
AB MICHAEL CLEAN CO LTD
CM BB CLEANER INC

编辑

使用回调,您可以扩展此逻辑以包含我的答案下方评论中提到的特殊情况,以匹配特定情况并进行条件替换。

这些特殊情况包括:

  • FONTAINE'SFONTAINE
  • PREMIUM-FIT AUTOPREMIUM FIT AUTO
  • 62325 W.C.62325 WC

我为正则表达式添加了一个新的替代:(\b[\'-]\b(?:[a-z\d] )?) 以在字母之间捕获 'S-(也可以是 -S 或类似的)并使用回调将其替换为空格(如果捕获组存在) .

我仍然建议使用多个正则表达式来完成此操作,但我想表明使用单个模式是可能的。

See code in use here

import re

strings = [
    "'W. & J. JOHNSON LMT.COM'",
    "'NORTH ROOF & WORKS CO. LTD.'",
    "'DAVID DOE & CO., LIMITED'",
    "'GEORGE TV & APPLIANCE LTD.'",
    "'LOVE BROS. & OTHERS LTD.'",
    "'A. B. & MICHAEL CLEAN CO. LTD.'",
    "'C.M. & B.B. CLEANER INC.'",
    "'FONTAINE'S PREMIUM-FIT AUTO 62325 W.C.'"
]

r = re.compile(r'(?<=\b[a-z\d])[^a-z\d]+(?=[a-z\d]\b(?![^a-z\d][a-z\d]))|(?<= ) *(?:\.com\b|[^a-z\d\s]+) *| *(?:\.com\b|[^a-z\d\s]+) *(?= )|(\b[\'-]\b(?:[a-z\d] )?)|(?<! )(?:\.com\b|[^a-z\d\s]+)(?! )', re.IGNORECASE)

def repl(m):
    return ' ' if m.group(1) else ''

for s in strings:
    print(r.sub(repl, s))

【讨论】:

  • 不应该这样添加吗:text=re.sub(r'(?&lt;=\b[a-z])[^a-z]+(?=[a-z]\b(?![^a-z][a-z]))|(?&lt;= ) *(?:\.com\b|[^a-z\s]+) *| *(?:\.com\b|[^a-z\s]+) *(?= )|(?&lt;! )(?:\.com\b|[^a-z\s]+)(?! )','' , text)
  • @JuanPerez 类似的东西,我目前无法测试,但我可以稍后回复您并确认。添加re.IGNORECASE
  • re.sub(r'(?
  • 我已经尝试了数据库中的其余模式,但是当单词包含撇号时我遇到了问题。例如,单词FONTAINE'S 应该是FOUNTAINE。此外,我还有一些包含连字符的单词,例如PREMIUM-FIT AUTO 应该是PREMIUM FIT AUTO。最后,我有以数字开头的单词,例如62325 W.C.,应该是62325 WC。感谢您的贡献:)
  • @JuanPerez 考虑到这些情况,单个正则表达式永远不会起作用,因为您不能用空替换并用空格替换某些位置(除非您使用回调)。考虑到这一点,您可以使用我添加到答案中的回调。
【解决方案3】:

这是我能用一个正则表达式模式得到的最简单的方法:

\.COM|(?&lt;![A-Z]{2}) (?![A-Z]{2})|[.&amp;,]| (?&gt;)&amp;

基本上,它会删除符合 3 个条件的字符:

  1. 文字“.COM”
  2. 前面或后面不带 2 个大写字母的空格
  3. 点、& 和逗号,无论它们出现在哪里
  4. & 后跟空格

演示:https://regex101.com/r/EMHxq9/2

【讨论】:

  • 我已经在数据库中运行了你的正则表达式,它匹配了所有的模式。查询持续了 16 秒,执行查询为 select regex_replace() from .. 您的正则表达式非常简单,可以找到所有模式 :) 最终版本看起来像这样 \.COM|(?&lt;![A-Z0-9]{2}) (?![A-Z]{2})|[.&amp;,()]|''[A-Z] 感谢您的帮助 :)
猜你喜欢
  • 2021-01-24
  • 1970-01-01
  • 2017-06-20
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-09
  • 1970-01-01
相关资源
最近更新 更多