python格式化字符串f-string小结

格式化字符串f-string概览

f-string,亦称为格式化字符串常量(formatted string literals),是Python3.6新引入的一种字符串格式化方法,该方法源于PEP 498 – Literal String Interpolation,主要目的是使格式化字符串的操作更加简便。f-string在形式上是以 f 或 F 修饰符引领的字符串(f’xxx’ 或 F’xxx’),以大括号 {} 标明被替换的字段;f-string在本质上并不是字符串常量,而是一个在运行时运算求值的表达式:

简单使用

f-string用大括号 {} 表示被替换字段,其中直接填入替换内容:

1
2
3
4
5
6
7
8
9
10
11
>>> name = 'Eric'
>>> f'Hello, my name is {name}'
'Hello, my name is Eric'

>>> number = 7
>>> f'My lucky number is {number}'
'My lucky number is 7'

>>> price = 19.99
>>> f'The price of this book is {price}'
'The price of this book is 19.99'

表达式求值与函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> f'A total number of {24 * 8 + 4}'
'A total number of 196'

>>> f'Complex number {(2 + 2j) / (2 - 3j)}'
'Complex number (-0.15384615384615388+0.7692307692307692j)'

>>> name = 'ERIC'
>>> f'My name is {name.lower()}'
'My name is eric'

>>> import math
>>> f'The answer is {math.log(math.pi)}'
'The answer is 1.1447298858494002'

引号、大括号与反斜杠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> f"He said {"I'm Eric"}"
File "<stdin>", line 1
f"He said {"I'm Eric"}"
^
SyntaxError: invalid syntax

>>> f'He said {"I'm Eric"}'
File "<stdin>", line 1
f'He said {"I'm Eric"}'
^
SyntaxError: invalid syntax

>>> f"""He said {"I'm Eric"}"""
"He said I'm Eric"
>>> f'''He said {"I'm Eric"}'''
"He said I'm Eric"

lambda表达式

1
2
3
4
>>> f'result is {(lambda x: x ** 2 + 1) (2)}'
'result is 5'
>>> f'result is {(lambda x: x ** 2 + 1) (2):<+7.2f}'
'result is +5.00

正则表达式

re.match 函数

1
2
3
4
5
6
7
8
9
re.match(pattern,string,flags=0)

pattern 匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

import re
print(re.match('www', 'www.shiyanlou.com').span()) # 在起始位置匹配
print(re.match('com', 'www.shiyanlou.com')) # 不在起始位置匹配

re.search 方法

1
2
3
4
5
6
7
8
9
re.search(pattern,string,flags=0)

pattern 要匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等

import re
print(re.search('www', 'www.shiyanlou.com').span()) # 在起始位置匹配
print(re.search('com', 'www.shiyanlou.com').span()) # 不在起始位置匹配

re.match 与 re.search 的区别

re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None;而 re.search 匹配整个字符串,直到找到一个匹配

检索和替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
re.sub(pattern,repl,string,count=0,flags=0)

pattern:正则表达式中模式字符串
repl:替换的字符串,也可为一个函数
string:要被查找替换的原始字符串
count:模式匹配后替换的最大次数,默认 0 表示替换所有的匹配

import re
phone="2004-959-559 # 这是一个国外电话号码"

#删除字符串中的 Python注释
num=re.sub(r'#.*$',"",phone) #r代表正则表达式,将在后面介绍具体规则
print("电话号码是:",num)

# 删除非数字(-)的字符串
num = re.sub(r'\D', "", phone)
print("电话号码是:",num)

re.compile 函数
compile 函数用于编译正则表达式,生成一个正则表达(Pattern)对象,供 match() 和 search() 这两个函数使用。

1
2
3
4
5
6
7
8
9
re.compile(pattern[,flags])

pattern: 一个字符串形式的正则表达式
flags:可选,表示匹配模式,比如忽略大小写,多行模式,具体参数为:
re.l 忽略大小写
re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
re.M 多行模式
re.S . 并且包括换行符在内的任意字符(. 不包括换行符)
re.X 为了增加可读性,忽略空格和 # 后面的注释

findall

1
2
3
4
5
6
7
8
9
10
11
12
findall(string[,pos[,endpos]])

string:待匹配的字符串
pos:可选参数,指定字符串的起始位置,默认为 0。
endpos:可选参数,指定字符串的结束位置,默认为字符串的长度。

import re
pattern=re.compile(r'\d+') #查找数字
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)
print(result1)
print(result2)

re.split

1
2
3
4
5
6
re.split(pattern, string[, maxsplit=0, flags=0])

pattern 匹配的正则表达式
string 要匹配的字符串
maxsplit 分隔次数,maxsplit =1 分隔一次,默认为 0,不限制次数
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等

KMP算法

字符串匹配是极为常见的一种模式匹配。简单地说,就是判断主串 T 中是否出现模式串 P,即 P 为 T 的子串。特别地,定义主串 T[0…n-1],模式串为 P [0…p-1],则主串与模式串的长度各为 n 与 p

暴力匹配

暴力匹配方法的思想非常朴素:

  1. 依次从主串的首字符开始,与模式串逐一进行匹配
  2. 遇到失配时,则移到主串的第二个字符,将其与模式串首字符比较,逐一进行匹配
  3. 重复上述步骤,直至能匹配上,或剩下主串的长度不足以进行匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s = "asdadadsfasdfasdfasdtest"
p = "test"


def brute_force_match(s, p):
s_len = len(s)
p_len = len(p)

for i in range(s_len):
j = 0
while j < p_len and i + j < s_len and s[i + j] == p[j]:
j += 1
if j == p_len:
return i
return -1

print(brute_force_match(s, p))

算法复杂度分析:i 在主串上移动 (n-p)次,匹配失败时,j 移动次数最多有 p-1 次。因此复杂度为 O(n*p)

对于暴力匹配而言,就存在重复匹配的现象。比如,第一次匹配失败时,主串,模式串失败匹配的位置的字符分别为 a 与 c ,下一次匹配时主串,模式串的起始位置分别为 T1 与 P[0];而在模式串中 c 之前是 ab ,未有重复结构,因此 T1与 P[0]肯定不能匹配上,这样造成了重复匹配。直观上,下一次匹配应从 T2 与 P[0] 开始。

KMP 算法原理

KMP 算法思想归纳如下:将主串 T 的第一个字符与模式串 P 的第一个字符进行匹配。如果相等,则依次比较 T 和 P 的下一个字符。如果不相等,则 主串 T 移动(已匹配字符数-对应的部分匹配值)位,继续匹配。

关于移动位数的解释:已匹配字符数,即当前已完成匹配的字符数量。部分匹配值就是前缀和后缀的最长的共有元素的长度。

前缀指除了最后一个字符以外,一个字符串的全部头部组合

后缀指除了第一个字符以外,一个字符串的全部尾部组合

举个例子:以”ABCDABD”为例

1
2
3
4
5
6
7
8
9
10
11
12
"A"的前缀和后缀都为空集,共有元素的长度为 0;
"AB"的前缀为[A],后缀为[B],共有元素的长度为 0;
"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度 0;
"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为 0;
“ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A”,长度为 1;
“ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB”,长度为 2;
"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为 0。

部分匹配表如下:

搜索值 A B C D A B D
部分匹配值 0 0 0 0 1 2 0

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def same_start_end(s):
"""最长前后缀相同的字符位数"""
n = len(s) # 整个字符串长度
j = 0 # 前缀匹配指向
i = 1 # 后缀匹配指向
result_list = [0] * n
while i < n:
if j == 0 and s[j] != s[i]: # 比较不相等并且此时比较的已经是第一个字符了
result_list[i] = 0 # 值为0
i += 1 # 向后移动
elif s[j] != s[i] and j != 0: # 比较不相等,将j值设置为j前一位的result_list中的值,为了在之前匹配到的子串中找到最长相同前后缀
j = result_list[j - 1]
elif s[j] == s[i]: # 相等则继续比较
result_list[i] = j + 1
j = j + 1
i = i + 1
return result_list


def kmp(s, p):
"""kmp算法,s是字符串,p是模式字符串,返回值为匹配到的第一个字符串的第一个字符的索引,没匹配到返回-1"""
s_length = len(s)
p_length = len(p)
i = 0 # 指向s
j = 0 # 指向p
next = same_start_end(p)
while i < s_length:
if s[i] == p[j]: # 对应字符相同
i += 1
j += 1
if j >= p_length: # 完全匹配
return i - p_length
elif s[i] != p[j]: # 不相同
if j == 0: # 与模式比较的是模式的第一个字符
i += 1
else: # 取模式当前字符之前最长相同前后缀的前缀的后一个字符继续比较
j = next[j]
if i == s_length: # 没有找到完全匹配的子串
return -1

参考文献

  1. https://blog.csdn.net/sunxb10/article/details/81036693
  2. https://blog.csdn.net/lzj_1314/article/details/89135217
  3. http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
  4. https://www.cnblogs.com/sfencs-hcy/p/10646437.html