python基础(9)--递归、二叉算法、多维数组、正则表达式

1.递归

  在函数内部,可以调其他函数,如果一个函数在内部调用它本身,这个函数就是递归函数。递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于裂解

递归算法解决问题的特点:

1)递归是在过程或函数里调用自身

2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口

3)递归算法解题通常显得很简洁,但递归算法解题的运行效率较低,所以一般不提倡用递归算法设计程序

4)在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序

递归算法所体现的“重复”一般有三个要求:

1)每次调用在规模上都有所缩小(通常是减半)

2)相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)

3)在问题的规模极小时必须用直接给出解答而不再进行递归调用,因为每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束

例:

def func(num):
    if num / 2 > 0:
        num -= 1
        print(num)
        num = func(num)
        print('quit')
    return num

func(10)

  二叉算法---具体实例可以通过递归实现二分查找

def binary_search(data_list, find_num):
    mid_pos = int(len(data_list) / 2)   #计算需要查找数据长度的一半
    mid_val = data_list[mid_pos]    #获取中间位置的那个值
    print(data_list)    #查看每次剩余筛选的数据列表
    if len(data_list) > 0:  #当列表长度大于0时,则一直查找
        if mid_val > find_num:  #如果中间的数比实际要查找的数大,那么这个数肯定在左边
            print("%s should be in left of [%s]" % (find_num, mid_val))
            binary_search(data_list[:mid_pos], find_num)
        elif mid_val < find_num:    #如果中间的数比实际查找的数小,那么这个数肯定在右边
            print("%s should be in right of [%s]" % (find_num, mid_val))
            binary_search(data_list[mid_pos:], find_num)
        else:
            print("Find %s" % find_num)

    else:   #否则就没有这个数
        print("cannot find [%s] in data_list" % find_num)


if __name__ == '__main__':
    primes = [1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
    binary_search(primes, 1)    #在列表里面查找1

'''
执行结果
[1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
1 should be in left of [41]
[1, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]
1 should be in left of [17]
[1, 3, 5, 7, 11, 13]
1 should be in left of [7]
[1, 3, 5]
1 should be in left of [3]
[1]
Find 1
'''

  多维数组交叉(多维数组旋转90度)

array = [[col for col in range(4)] for row in range(4)] #初始化一个4*4数组

for row in array:   #旋转前先看看数组长啥样
    print(row)


for i, row in enumerate(array):
    for index in range(i, len(row)):
        tmp = array[index][i]   #将每一列数据在每一次遍历前,临时存储
        array[index][i] = array[i][index]   #将每一次遍历的值,赋值给交叉的列
        print(tmp, array[i][index])
        array[i][index] = tmp   #将之前保存的交叉列的值,赋值给交叉行的对应值
    for r in array:     #打印每次交换后的值
        print(r)



'''
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2, 3]
0 0
0 1
0 2
0 3
[0, 0, 0, 0]
[1, 1, 2, 3]
[2, 1, 2, 3]
[3, 1, 2, 3]
1 1
1 2
1 3
[0, 0, 0, 0]
[1, 1, 1, 1]
[2, 2, 2, 3]
[3, 3, 2, 3]
2 2
2 3
[0, 0, 0, 0]
[1, 1, 1, 1]
[2, 2, 2, 2]
[3, 3, 3, 3]
3 3
[0, 0, 0, 0]
[1, 1, 1, 1]
[2, 2, 2, 2]
[3, 3, 3, 3]
'''

  正则表达式

  字符串是编程时涉及到最多的一种数据结果,对字符串进行操作的需求几乎无处不在,比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以服用

  正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串,在文本处理方面功能非常强大,也经常用作爬虫,来爬取特地内容,Python本身不支持正则,但是通过导入re模块,Python也能用正则表达式,下面就来讲一下python正则表达式的用法

Python支持的正则表达式元字符和语法

语法 说明 表达式实例 完整匹配的字符串
字符
一般字符 匹配自己 abc abc
. 匹配任意字符“ ”除外
DOTALL模式中(re.DOTALL)也能匹配换行符
a.b abc或abc或a1c等
[...] 字符集[abc]表示a或b或c,也可以-表示一个范围如[a-d]表示a或b或c或d a[bc]c abc或adc
[^...] 非字符集,也就是非[]里的之外的字符 a[^bc]c adc或aec等
预定义字符集(也可以系在字符集[...]中)
d 数字:[0-9] adc a1c等
D 非数字:[^0-9]或[^d] aDc abc等
s 空白字符:[<空格> fv] asc a b等
S 非空白字符:[^s] aSc abc等
w 字母数字(单词字符)[a-zA-Z0-9] awc abc或a1c等
W 非字母数字(非单词字符)[^w] aWc a.c或a_c等
数量词(用在字符或(...)分组之后)
* 匹配0个或多个前面的表达式。(注意包括0次) abc* ab或abcc等
+ 匹配1个或多个前面的表达式。 abc+ abc或abcc等
? 匹配0个或1个前面的表达式。(注意包括0次) abc? ab或abc
{m} 匹配m个前面表达式(非贪婪) abc{2} abcc
{m,} 匹配至少m个前面表达式(m至无限次) abc{2,} abcc或abccc等
{m,n} 匹配m至n个前面的表达式 abc{1,2} abc或abcc
边界匹配(不消耗待匹配字符中的字符)
^ 匹配字符串开头,在多行模式中匹配每一行的开头 ^abc abc或abcd等
$ 匹配字符串结尾,在多行模式中匹配每一行的结尾 abc$ abc或123abc等
A 仅匹配字符串开头 Aabc abc或abcd等
仅匹配字符串结尾 abc abc或123abc等
匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。    
B 匹配非单词边界。'erB' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。    
逻辑、分组
| 或左右表达式任意一个(短路)如果|没有在()中表示整个正则表达式(注意有括号和没括号的区别) abc|def
ab(c|d)ef
abc或def
abcef或abdef
(...) 分组,可以用来引用,也可以括号内的被当做一组进行数量匹配后接数量词 (abc){2}a abcabca
(?P<name>...) 分组别名,给分组起个名字,方便后面调用    
<number> 引用编号为<number>的分组匹配到的字符串(注意是配到的字符串不是分组表达式本身) (d)abc1 1ab1或5ab5等
(?=name) 引用别名为name的分组匹配到的字符串(注意是配到的字符串不是分组表达式本身) (?P<id>d)abc(?P=id) 1ab1或5ab5等
       
 
   数量词的贪婪模式与非贪婪模式
  正则表达式通常用于在文本中查找匹配的字符串,Python里数量词默认是贪婪的(在少数语言里也可能默认非贪婪),总是尝试匹配尽可能多的字符,非贪婪模式则相反,总是尝试屁屁额尽可能少的字符,例如:正则表达式"ab*"如果用于查找"abbbc",将 找到"abbb"。而使用非贪婪的数量词"ab*?",将找到"a"
 
   Python的re模块
  Python通过re模块提供对正则表达式的支持,使用re的一般步骤是先将正则表达式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文本并获得匹配结果(一个Match实例),最后使Match实例获得信息,进行其他操作
import re

#将正则表达式编译成Pattern对象
pattern = re.compile(r'hello')

#使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None
match = pattern.match('hello world!')

if match:
    #使用Match获得分组信息
    print(match.group())

#执行结果hello

  

  还可以直接使用re模块的方法的时候直接传递正则表达式参数来完成

 
match = re.match('hello', 'hello world')
print(match.group()

  re模块常用方法

1)findall(获取字符串中所有的匹配字符串)

import re
l = re.findall('d', '4k3sadsd8,9') #d代表数字,将匹配带的元素放到一个列表里
print(l)    #['4', '3', '8', '9']

print(re.findall('w', 'de..,_2'))  #['d', 'e', '_', '2']匹配字母数字下划线

print(re.findall('^dk', 'dkwej,ds34,')) #以dk开头的['dk']

print(re.findall('^dk', 'des,423')) #[]

print(re.findall('d{3,5}', 'sddddd'))   #取前面一个字符d的3到5次

print(re.findall('s{2}', 'ssaesssfgss'))    #匹配前一个字符s两次['ss', 'ss', 'ss']

print(re.findall('a*x', 'aaaaaax')) #['aaaaaax']匹配前面一个字符0次或多次,贪婪匹配

print(re.findall('d*', 'swe32452'))    #['', '', '', '32452', '']

print(re.findall('a+c', 'aaaccc'))  #['aaac']匹配前面一个字符的一次或多次,贪婪匹配

print(re.findall('a?c', 'aaaccc'))  #['ac', 'c', 'c']匹配前面一个字符的0次或1次

print(re.findall('a[.]s', 'adfff afs')) #.在[]里面失去意义,所以为[]

print(re.findall('[a-z]', 'we3.j4_re')) #['w', 'e', 'j', 'r', 'e']

print(re.findall('[^a-z]', 'we3.j4_re'))    #['3', '.', '4', '_']取反

print(re.findall('ax$', 'dsadax'))  #以ax结尾['ax']

print(re.findall('a(d+)b', 'a42323b')) #['42323']

print(re.findall('a(d+?)b', 'a42323b'))#['42323']前后均有限定条件,则非贪婪模式,失效

print(re.findall('a(d+)', 'a23b')) #['23']

print(re.findall('a(d+?)', 'a23b'))#['2']加上?变成非贪婪模式

  findall高级用法: ?:

  默认取分组()内的信息,但是想让分组外的匹配信息也渠道,就要用?:

import re
l = re.findall("www.(baidu|laonanhai).com", "asd www.baidu.commwww.laonanhai.com")
print(l)    #['baidu', 'laonanhai']默认取括号内的信息

l = re.findall("www.(?:baidu|laonanhai).com", "asd www.baidu.commwww.laonanhai.com")
print(l)    #['www.baidu.com', 'www.laonanhai.com']

  finditer(): 迭代查找

import re
p = re.compile('d+')
iterator = p.finditer('12 drumm44ers drumming, 11 ... 10 ...')
for match in iterator:
    print(match.group())
    print(match.span())


'''
执行结果
12
(0, 2)
44
(8, 10)
11
(24, 26)
10
(31, 33)
'''

2)match(pattern, string, flag=0)

·正则表达式

·要匹配的字符串

·标志位,用于控制正则表达式的匹配方式

注意:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符'$'。 

返回值:如果匹配成功返回match对象,否则返回None

Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息

方法:
1.group([group1, ...]):
获得一个或多个分组截获的字符串,指定多个参数时将以元组形式返回,group1可以使用编号也可以使用别名,编号0代表整个匹配的子串,不填写参数时,返回group(0),没有截获字符串的返回None,截获了多次的返回最后一次的子串
2.group([default]):
以元组形式返回全部分组截获的字符串,相当于调用group(1,2,...last),default表示没有截获字符串的组以这个代替,默认为None
3.groupdict([default]):
返回以有别名的组的别名为键、以该组截获的子串为值得字典,没有别名的组不包含在内,default含义同上
4.start([group]):
返回以指定的组截获的子串string中的其实索引(子串第一个字符的索引),group默认值为0
5.end([group])
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1),group默认值为0
6.span([group]):
返回(start(group),end(group))
7.expand(template):
将匹配到的分组带入template中然后返回,template中可以使用id或g<id>、g<name>引用分组,但不能使用编号0,id与g<id>是等价的;但10将被认为是第10个分组,如果你想表达1之后是字符'0',只能使用g<1>0。

3)search(pattern,string,flag=0)

  根据模型去字符串中匹配指定内容,匹配单个,只匹配一次,可以结合split将匹配到内容分割、拼接,然后再次循环查找,因为findall尽管可以找到所以,但是在处理分组()时候分组外的内容匹配不到,findall返回的是列表

可以通过一个实例来看下match跟search的区别

import re
s = 'hello world'
print(re.match('ello', s))  #输出None

print(re.search('ello', s)) #<_sre.SRE_Match object; span=(1, 5), match='ello'>

'''
可以看到match只匹配开头,search则可以从中间匹配,只要有就能匹配到
'''

4)group和groups

group(0)  显示全部

group(1)  显示第一个分组()

group(2)  显示第二个分组()

如果没有分组或超出分组个数就会报错

5)split(pattern, string, maxsplit=0, flags=0)

  按照匹配子串将字符串进行分割,返回分割的列表,maxsplit用于指定最大分割次数,不指定将全部分割。

import re
s = 'one1two2three3four4'
print(re.split('d+', s))

#['one', 'two', 'three', 'four', '']

6)re.compile(strPattern[, flag]): compile 编译方法

  如果一个匹配规则,以后要使用偶次,就可以先将其编译,以后就不用每次都在去写匹配规则

这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。第二个参数flag是匹配模式,取值可以使用按位或运算符'|',表示同时生效,比如re.l | re.M

  可以把正则表达式编译成一个正则表达式对象。可以把那些经常使用的正则表达式编译成正则表达式对象,可以提高一定的效率,下面是一个正则表达式对象的例子

import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
regx = re.compile('w*oow')
print(regx.findall(text))   #查找所有包含'oo'的单词['ood', 'ool']

7)sub(pattern, repl, string, count=0, flag=0)

  用于替换匹配的字符串,pattern内必须为正则表达式,不能是正则比倒是search或findall查找到的赋值变量

  比如我的计算器处理括号的方法,用正则search匹配到后,不能直接将变量输入sub的pattern,因为不起做哟用

  sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]): 

  使用repl替换string中每一个匹配的子串后返回替换后的字符串

  当repl是一个字符串时,可以使用id或g<id>、g<name>引用分组,但不能使用编号0

  当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)

import re
print(re.subn('d', 'ZZ', '34*,5sdfs,4hsdf'))

#('ZZZZ*,ZZsdfs,ZZhsdf', 4)
#subn方法,返回总共替换的次数

  实例:假设有一个四则元素表达式'--(1.1+1+1-(-1)-(1+1+(1+1+2.2)))+-----111+--++--3-+++++++---+---1+4+4/2+(1+3)*4.1+(2-1.1)*2/2*3',遵循奇数个负号等于正否则为负的原则进行替换,我们可以这样

import re
s = '--(1.1+1+1-(-1)-(1+1+(1+1+2.2)))+-----111+--++--3-+++++++---+---1+4+4/2+(1+3)*4.1+(2-1.1)*2/2*3'
def replace_sign(expression):
    def re_sign(m):
        if m:
            if m.group().count('-') % 2 == 1:
                return '-'
            else:
                return '+'
        else:
            return ''

    expression = re.sub('[+-]{2,}', re_sign, expression)
    return expression

s = replace_sign(s)
print(s)


#+(1.1+1+1-(-1)-(1+1+(1+1+2.2)))-111+3-1+4+4/2+(1+3)*4.1+(2-1.1)*2/2*3

正则补充 match search示例

import re
print(re.match('www', "www.baidu.com"))  # 打印匹配结果
print(re.match('www', "www.baidu.com").group())  # 打印匹配到的结果
print(re.match('www', "www.baidu.com").end())  # 打印匹配到的结束位置,不包含该索引字符
print(re.match('www', "www.baidu.comw").endpos)  # 打印最后一个字符串位置
print(re.match('www', "www.baidu.com").groupdict())
print(re.match('www', "www.baidu.com").groups())
print(re.match('www', "www.baidu.com").lastindex)
print(re.match('www', "www.wwwbaidu.com").pos)  # 打印匹配索引
print(re.match('www', "www.baidu.com").re)  # 打印编译(解析公式)
print(re.match('ww', "www.baidu.com").span())  # 打印匹配分片的位置
print(re.match('www', "www.baidu.com").start())  # 打印匹配开始索引
print(re.match('www', "www.baidu.com").string)  # 打印被匹配字符串

print(re.search("www", "www.www.com"))
print(re.search("www", "qq.www.com"))
print(re.search("www", "www.www.com").string)
print(re.search("www", "www.www.com").start())
print(re.search("www", "qq.www.com").start())
print(re.search("www", "qq.www.com").span())  # 返回匹配到的索引位置
print(re.search("www", "www.www.com").span())
print(re.search("www", "qq.www.com").group())
print(re.search("www", "qq.www.com").groups())
print(re.search("www", "qq.www.com"))

# search 与match,
# search 从头匹配,只要找到则停止匹配,如果有多个被匹配,只返回最先匹配到的位置字符
# match  从头匹配,开头位置匹配不到就不继续匹配

正则分组匹配模式 ()

a = "123abc456"
print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group())  # 模式()分组匹配
print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(0))  # 显示第0组,也就是所有分组
print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(1))  # 显示第一组分组
print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).group(2))
print(re.search("([0-9]*)([a-z]*)([0-9]*)", a).groups())  # 返回元组形式的分组匹配

匹配中文

re.findall("[u4e00-u9fa5]+", string)
contactInfo = 'Oldboy School, Beijing Changping Shahe: 010-8343245'
match = re.search('(?P<last>w+s+w+),(?P<first>s+w+s+w+s+w+): (?P<phone>S+)', contactInfo)
print(match.group("last"))  # 打印自定义标签内容
print(match.group('first'))
print(match.group("phone"))

'''
Oldboy School
 Beijing Changping Shahe
010-8343245
'''