Bootstrap

【Python】主字符串中查找子字符串:滑动窗口、正则表达式、递归检查

一、题目

In this challenge, the user enters a stirng and a substring. You have to print the number of times that the substring occurs in the given string. String traversal will tack place from left to right, not from right to lef.

NOTE: String letters are case-sensitive.

Input Format

The first line of input contains the original string. The next line contains the substring.

Sample Input

ABCDCDC

CDC

Sample Output

2

Concept

Some string processing examples, such as these, might be useful.

There are a couple of new concepts:

In Python, the length of a string is found by the function len(s), where s is the string.

To traverse through the length of a string, use a for loop:

for i in range(0, len(s)):

        print(s[i])

A range function is used to loop over some length:

range(0,5)

Here, the range loops over 0 to 4. 5 is exclued.

从题目可知,本程序需要查找字符串,且允许重叠情况出现,即 

ABCDCDC 中,"CDCDC" 被认为存在 2个"CDC" 字符串,也就是中间的 C 重叠了

二、方法1: 滑动窗口

1、通过逐个字符移动窗口来检查每个可能的子字符串。

def count_substring(string, sub_string):
    count = 0 
    # 确定 sub_string 字符串长度
    len_sub = len(sub_string)
    
    # 确定循环次数,只需要确认最后一个滑动窗口的起始位置。
    for i in range(len(string) - len_sub + 1):
        if string[i:i+len_sub] == sub_string
            count += 1
    return count

if __name__ == '__main__':
    string = input().strip()
    sub_string = input.strip()
    count = count_substring(string, sub_string)
    print(count)

 

2、解读 

  1. len_sub = len(sub_string) :计算子字符串长度,并存储在变量 len_sub 中。
  2. len(string) - len_sub + 1 : for 循环中遍历的最后一个字符的位置不是 len(string) , 是为了确保在每一个索引处,剩余的字符串长度都与子字符串长度一致。
  3. if string[i:i+len_sub] == sub_string :从 i 到 i+len_sub ,截取与sub_string 相同长度的字符串,然后比较两个字符串的值是否一致
  4. count += 1 : 即 count = count + 1 的简写
  5. string = input().strip() :获取用户输入的字符串,并用strip() 方法去除可能得首尾空白字符。

三、方法2:正则表达式(使用重叠匹配)

1、使用正则表达式的 re 模块和 finditer() 方法来找到所有匹配的子字符串,包括重叠的。

import re

def count_substring(string, sub_string):
    count = 0
    
    for match in re.finditer(r'(?={})'.format(re.escape(sub_string)), string):
        count += 1

    return 0


if __name__ == '__main__':
    
    string = input.strip()
    sub_string = input.strip()
    
    count = count_substring(string, sub_string)
    print(count)

2、解读 

        1.  import re :导入 re 模块,该模块提供对正则表达式的访问。

        2. for match in re.finditer(r'(?={})'.format(re.escape(sub_string)), string) :

       在当前位置后面能够匹配经过转义的 sub_stirng 字符串,但不消耗任何字符(即不移动匹配位置)。

        * 使用 re.finditer 函数查找主字符串 string 中所有的子字符串 sub_string 的出现位置。re.finditer 返回一个迭代器,产生每个匹配的 Match 对象。

        * r'(?={})' 是一个正则表达式的前瞻断言,其中{}将被 .format(re.escape(sub_string)) 替换为转以后的子字符串。

        (?:...)是一种非捕获组,用于指定一个条件,但这个条件不被捕获用于后续匹配。

        (?=...)表示“在不消耗任何字符的情况下,断言某个位置后面能够匹配...”。在这里,{}是一个占位符,稍后将被替换为实际的模式。

        * re.escape(sub_string): 是Pyhton中 re(正则表达式)模块的一个函数,它的作用是转义字符串sub_string 中的所有正则表达式特殊字符,即将sub_string 中的每个特殊字符前添加一个反斜杠 \ ,这样正则表达式中就能安全地使用这些字符。

(正则表达式的特殊字符包括: .   *   +   ?   ^   $   {  }   [  ]   |   \  

        * str.format() 方法用于格式化字符串,将re.escape(sub_sting) 的结果替换到 r'(?={})' 中的{}占位符处。

3、何为之“不消耗任何字符”?

      在正则表达式中,“不消耗字符”指的是在匹配过程中,某个匹配模式虽然检查了字符串中的字符,但并不实际“使用”或“消耗”这些字符。也就是说,即使模式匹配成功,正则表达式的引擎也不会向前移动字符位置指针,后续的匹配仍然可以从相同的位置开始。

这种特性在正则表达式的断言(assertions)中非常有用,断言包括:

  • 正向前瞻断言 ( Positive Lookahead) : (?:=...) 或 (?=...) ,表示在当前位置后面能够匹配 ... 
  • 正向负瞻断言 ( Negative Lookahead) : (?:!...) 或 (?!...) ,表示在当前位置后面不能匹配...

示例:

假设有一字符串“aabaab”,使用正则表达式 r'(?=ab)':

  • 在索引 1 处,"aab" 满足 r'(?=ab)' ,因为"ab"出现在"aab"后面,所以这里会有一个匹配。
  • 即使匹配成功,正则表达式的引擎不会移动,它仍然处于字符串的索引1处,可以继续检查下一个"ab"。

因此,如果你使用循环或连续的查找,可以在同一个位置多次找到满足条件的匹配,这就是前瞻断言“不消耗字符”的实际应用。

四、方法3:递归检查

1、通过递归检查每个可能的起始位置,并计算匹配次数。 

def count_substring(stirng, sub_string):
    # 如果已经检查完所有字符,返回0
    if index >= len(string):
        return 0
    
    # 从index=0位置开始,类似滑动窗口一样匹配主字符串当前位置的字符与 sub_string 是否相同
    if string[index:index+len(sub_string)] == sub_string:
        # 计算当前匹配,并且递归检查剩余的字符串
        return 1 + count_substring(string, sub_string, index + 1)
    else:
        # 如果当前位置不匹配,递归检查下一个位置
        return count_substring(string, sub_string, index + 1)

if __name__('__main__'):
    string = input().strip()
    sub_sting = input().strip()
    count = count_substirng(string, sub_string)
    print(count)

2、解读

        1、函数定义:定义了一个名为 count_substring 的递归函数,接收三个参数:string(主字符串)、sub_string(子字符串)和 index(当前检查的索引位置,默认为0)

        2、基础条件:如果 index 大于或等于主字符串的长度,返回0,因为已经检查完所有字符。

        3、检查当前位置的匹配:如果从 index 开始,长度为 len(sub_string) 的子字符串与 sub_string 相等,则当前位置匹配。

        4、递归调用:如果当前位置匹配,返回1(当前匹配)加上从下一个字符开始的递归调用结果。否则返回从下一个字符开始的递归调用结果。

        5、主程序:读取用户输入的主字符串和子字符串,调用count_substring函数,并打印结果。

通过递归调用,确保每个字符串都被计算,包括重叠计算的情况。

 

;