【问题标题】:How to find three consecutive letter and number patterns in a string如何在字符串中找到三个连续的字母和数字模式
【发布时间】:2022-01-19 22:04:58
【问题描述】:

我正在处理包含字母、数字和特殊字符的字符串。

我正在尝试创建一个 Python 函数来检测 3 个“连续模式”并根据它们的存在返回 true 或 false。模式的一个示例是以下“1/y2”。

这里的四个示例字符串是(y1 和 b1 以及后跟特殊字符 '*' 的模式将被输入,见下文)

  1. string1 = '1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5' -> 真(因为 1/y2、1/y3、1/y4是连续的)
  2. string2 = '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5' -> False(没有连续模式,因为第三个元素有特殊字符'*'并且需要被排除)
  3. string3 = '1/y2;1/y3;9/y4' -> False(不是连续模式,因为最后一个元素以 9 开​​头。)
  4. string4 = '1/y2;1/y3;1/y4*' -> False(不是连续模式,因为最后一个元素后跟 '*')
  5. string5 = '1/y2;1/y3;9/y4,2/y2;2/y3;2/y4' -> True(连续模式为 2/y2;2/y3;2/y4)
  6. string6 = '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3' -> False(虽然有 3 个以 1 开头的连续模式和 3 个以 2 开头的连续模式, 包含 'y1' 和 'b1' 的模式需要被忽略)。
  7. string7 = = '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3;2/b4' -> True (3个连续模式是2/b2;2/b3 ;2/b4)

这是我的处理过程:

  1. 以“b”或“y”开头后跟数字(即 b2、b4、y11、y6)的 MATCH 模式
  2. 如果前一个模式后面有一个“”,则从匹配中排除(即 b2
  3. 当 'b' 或 'y' 后跟 1(即 b1, y1)时,从前一个模式的匹配中排除
  4. 构建一个从正则表达式获取结果并查找连续模式的函数。

现在我只有一个部分工作的代码。我可以使用 itertools 和 itemgetter 从正则表达式结果中找到连续的数字。

def three_consecutive_by_pattern(text):
pattern=r'\w(\d+)(?:;|$)'
# extract b or y pattern the ones followed by *
by_list = re.findall(pattern, text)

# convert result from regex to 'numbers'
by_list = [int(x) for x in by_list]

# find sequence of two or more consecutive numbers
consecutive_list = []
for k, g in itertools.groupby(enumerate(by_list), lambda x: x[1]-x[0]):
    final_list = list(map(itemgetter(1), g))    
    consecutive_list.append(final_list)

# from list remove matches for y1 and b1
list_more_than_3_by = [[ion for ion in sub if ion != 1] for sub in consecutive_list]
list_more_than_3_consecutive_by = []

# find sequence of more than 3 numbers. If present return true
for item in list_more_than_3_by:
    if len(item) >= 3:
        list_more_than_3_consecutive_by.append(item)
if list_more_than_3_consecutive_by:
    return True
else:
    return False 

这在字符串 1 和字符串 2 上正确执行,但在字符串 3 和字符串 4 上失败。

我正在考虑将正则表达式更改为:

r'([1-9]\/\w\d+)(?:;|$)'

但是当我“将结果从正则表达式更改为数字”时我需要进行更改,并且我相信创建多个列表,具体取决于模式的开始方式?

想法?

谢谢!

【问题讨论】:

  • 假设数字可以是任何数量级(例如,'1y1000;1y1001;1y1002' 满足要求),使用正则表达式将无济于事,因为尽管有些人很聪明,但它们无法执行算术运算。
  • 不清楚,但是如果您只需要检查前三个元素中的第一个数字并验证模式,请尝试like this demo。 @CarySwoveland 解释说,仅通过正则表达式无法检查递增的数字(您需要一个回调函数)。
  • 正则表达式无法计数。你问的是这个吗?
  • 不,正则表达式只是起点。其余的功能是我需要调整的。只有当模式共享相同的前三个字符时,它现在才能正常工作,即。 1/y2;1/y3;1/y4,这些我认为是连续的,所以函数应该返回 true。但如果第一个或第三个字符不同,例如 1/y2;1/y3;9/y4,则不起作用,因为第三个模式中的 9,这些不是连续的,应该返回 false。

标签: python regex text-parsing


【解决方案1】:

这是使用前瞻的一个很好的用例:

import re


samples = [
    '1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5',
    '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5',
    '1/y2;1/y3;9/y4',
    '1/y2;1/y3;1/y4*'
]

regex = re.compile(r'(\d/[a-z])(\d+)(?=;\1(\d+);\1(\d+);)')


result = {}
for n, sample in enumerate(samples):
    result[sample] = False
    for match in re.findall(regex, sample):
        if int(match[-1]) == int(match[-2]) + 1 == int(match[-3]) + 2:
            print(f'True: {n}, {sample} {match[1:]}')
            result[sample] = True
        else:
            print(f'False: {n}, {sample} {match[1:]}')
    else:
        print(f'False: {n}, {sample} (no match)')

print(result)

结果:

True: 0, 1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5 ('1', '2', '3')
True: 0, 1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5 ('2', '3', '4')
False: 0, 1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5 ('3', '4', '6')
False: 0, 1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5 (no match)
False: 1, 1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5 (no match)
False: 2, 1/y2;1/y3;9/y4 (no match)
False: 3, 1/y2;1/y3;1/y4* (no match)
{'1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5': True, '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5': False, '1/y2;1/y3;9/y4': False, '1/y2;1/y3;1/y4*': False}

输出显示哪些样本被发现为Trueresult 包含每个样本,以及是否至少有一个匹配项也符合条件。

魔力在于正则表达式:

(\d/[a-z])(\d+)(?=;\1(\d+);\1(\d+);)

这匹配<single digit>/<single letter><1 or more digits> 形式的任何内容,后跟<single digit>/<single letter> 的相同模式和<1 or more digits> 的任何序列 - 这四组(识别模式和三组数字)在每个匹配项中,并且比赛可以重叠,因为比赛只涉及第一组,其余的在前瞻(?=...)

希望以上内容有助于解释,更清晰的实现:

import re


samples = [
    '1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5',
    '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5',
    '1/y2;1/y3;9/y4',
    '1/y2;1/y3;1/y4*'
]

regex = re.compile(r'(\d/[a-z])(\d+)(?=;\1(\d+);\1(\d+);)')


def valid(sample):
    for match in re.findall(regex, sample):
        if int(match[-1]) == int(match[-2]) + 1 == int(match[-3]) + 2:
            return True
    return False


result = {sample: valid(sample) for sample in samples}
print(result)

或者,如果您不喜欢全局变量(以及谁喜欢),您可以将 valid 编写为单个函数,并将预编译的正则表达式包含在部分函数中:

import re
from functools import partial


valid = partial(
    lambda sample, regex: any(int(match[-1]) == int(match[-2]) + 1 == int(match[-3]) + 2
                              for match in re.findall(regex, sample)),
    regex=re.compile(r'(\d/[a-z])(\d+)(?=;\1(\d+);\1(\d+);)')
)

这是我要使用的版本,除非valid() 函数无论如何都是某个对象的方法,在这种情况下,您可以将正则表达式设为属性或类属性。

编辑:OP 在 cmets 中指出了两个缺点:

  • 应该接受y1b1 来开始这个系列;
  • 如果系列的最后一个元素在字符串的末尾,则不需要;

这是修改后的解决方案:

import re


samples = [
    '1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5',
    '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5',
    '1/y2;1/y3;9/y4',
    '1/y2;1/y3;1/y4*',
    '1/y2;1/y3;9/y4,2/y2;2/y3;2/y4',
    '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3',
    '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3;2/b4'
]

regex = re.compile(r'(\d/([a-z]))(\d+)(?=;\1(\d+);\1(\d+)(?:;|$))')


def valid(sample):
    for match in re.findall(regex, sample):
        if ((int(match[-1]) == int(match[-2]) + 1 == int(match[-3]) + 2) and
                (int(match[-3]) > 1 or match[1] not in ['y', 'b'])):
            return True
    return False


result = {sample: valid(sample) for sample in samples}
print(result)

结果:

{'1/y1;1/y2;1/y3;1/y4;1/y6;2/b4;5/b5': True, '1/y2;1/y3;1/y4*;2/y6;2/b4;8/b5': False, '1/y2;1/y3;9/y4': False, '1/y2;1/y3;1/y4*': False, '1/y2;1/y3;9/y4,2/y2;2/y3;2/y4': True, '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3': False, '1/y1;1/y2;1/y3;2/b1;2/b2;2/b3;2/b4': True}

请注意,这是通过在其自己的组中捕获一系列字母并在第一个数字是 1 时检查它不是 'y''b' 并检查尾随 ; 或 end- 来实现的前瞻中的离线$

作为一般提示:如果您在 StackOverflow 上获得或找到类似的答案,请不要只是复制代码并假设编写它的人不知何故知道魔法。该代码之所以有效,是因为逻辑相对简单。想出它并不总是那么容易,但是给出答案,最好利用你的时间来阅读它的每一个细节,并试着理解为什么每一个小部分都在那里。对所提供的解决方案的真正理解比实际解决方案更有价值,并且可以让您编写自己的解决方案、构建解决方案并自信地应用它,因为您知道它确实可以解决您的问题。

【讨论】:

  • 请注意,示例0 的解释版本也会打印no match,因为它永远不会从循环中中断,所以在处理完所有匹配后,它仍然会显示“不匹配” - 这可能已修复,但我认为这对解释没有太大帮助。
  • 非常感谢,代码简洁多了!不幸的是,我并不清楚,这场比赛不仅涉及第一组。我包括了一些例外情况,例如 string5、string6、string7。在那些代码上不能正确执行。我应该设置多个正则表达式,然后将该函数应用于每个正则表达式的结果吗?有什么想法吗?
  • string5 和 string7 不匹配,因为它们不以 ; 结尾 - 但我看到你希望它也能在字符串的末尾工作。忽略b1y1 的特殊情况是我完全错过的,但可以添加。我将添加一个涵盖这些的解决方案,但如果您理解该解决方案,添加它应该是相当简单的。
猜你喜欢
  • 2020-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多