函数练习
1.把一个字典扁平化,源字典为{'a':{'b':1,'c':2}, 'd':{'e':3,'f':{'g':4}}} 。
上面字典的扁平化可以转化为下面的字典:{“a.c”:2,"d.e":3,"d.f.g":4,"a.b":1}
source = {'a':{'b':1,'c':2}, 'd':{'e':3,'f':{'g':4}}} target = {} #recursion def flatmap(src,prefix = ""): for k,v in src.items(): if isinstance(v,(list,tuple,set,dict)): flatmap(v,prefix=prefix+k+".")#递归调用 else: target[prefix+k]=v flatmap(source) print(target) 结果为: {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
像一般这样的函数都会生成一个新的字典,上面的代码借用了外部的变量,破坏了函数的封装,因此可以对上面的函数稍微改造下,dest字典可以由内部来创建,当然也可以外部提供。
source = {'a':{'b':1,'c':2}, 'd':{'e':3,'f':{'g':4}}} #recursion def flatmap(src,dest=None,prefix = ""): if dest ==None: dest = {} for k,v in src.items(): if isinstance(v,(list,tuple,set,dict)): flatmap(v,dest,prefix=prefix+k+".")#递归调用 else: dest[prefix+k]=v return dest print(flatmap(source)) 结果为: {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
上面的函数有一个缺点,那就是将内部的字典暴露给了外部,能否函数提供一个参数源字典,返回一个新的扁平化字典?递归的时候要把目标字典的引用传递多层,这个时候应该怎么处理?
source = {'a':{'b':1,'c':2}, 'd':{'e':3,'f':{'g':4}}} #recursion def flatmap(src): def _flatmap(src,dest=None,prefix=""): for k,v in src.items(): key = prefix+k if isinstance(v,(list,tuple,set,dict)): _flatmap(v,dest,key+".")#递归调用 else: dest[key]=v dest = {} _flatmap(src,dest) return dest print(flatmap(source)) 结果为: {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}
2.实现base64编码,要求自己实现算法,不用库。
将输入每3个字节断开,拿出一个3个字节,每6个bit断开成4段。2**6=64,因此有了base64的编码表。每一段当做一个8bit看它的值,这个值就是base64编码表的索引值,找到对应字符。再取出3个字节,同样处理,直到最后。
举例:
abc对应的ASCII码为:0x61 0x62 0x63
01100001 01100010 01100011#abc
011000 010110 001001 100011
00011000 00010110 00001001 00100011 #每6位补齐为8位
24 22 9 35
末尾的处理?
- 正好3个字节,处理方式同上。
- 剩1个字节或2个字节,用0补满3个字节。
- 补0的字节用=表示。
# 自己实现对一段字符串进行base64编码 alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwsyz0123456789+/" teststr = "abcd" tsetstr ="Manma" def base64(src): ret = bytearray() length = len(src) #用r记录补0的个数 r = 0 for offset in range(0,length,3): if offset+3<=length: triple = src[offset:offset+3] else: triple = src[offset:] r = 3-len(triple) triple = triple +"\x00"*r#补几个0 #print(triple,r) #将3个字节看成一个整体转成字节bytes,大端模式 #abc=>0x616263 b = int.from_bytes(triple.encode(),"big")#小端模式为"little" print(hex(b)) #01100001 01100010 01100011 #abc #011000 010110 001001 100011 #每6位断开 for i in range(18,-1,-6): if i==18: index = b>>i else: index = b>>i &0x3F #0b0011 1111 ret.append(alphabet[index])#得到base64编码的列表 #策略是不管是不是补0,都补满,只有最后一次可能出现补0的 #在最后替换掉就是了,代码清晰,而且替换至多2次 #在上一个循环中判断r!=0,效率可能会更高些 for i in range(1,r+1):#1到r,补几个0替换几个= ret[-i]=0x3D return ret print(base64(tsetstr))
结果为:
0x4d616e 0x6d6100 bytearray(b'TWFubWE=')
#base64实现
import base64
print(base64.b64encode(teststr.encode()))
结果为:
b'TWFubWE='
练习3:求两个字符串的最长公共子串
思考:
s1 = "abcdefg"
s2 = "defabcd"
方法一:矩阵算法
让s2的每一个元素,去分别和s1的每一个元素比较,相同为1 ,不同为0,有下面的矩阵。
上面都是s1的索引。
看与斜对角平行的线,这个线是穿过1的,那么最长的就是最长子串。
print(s1[3:3+3])
print(s1[0:0+4])最长
矩阵求法还需要一个字符扫描最长子串的过程,扫描的过程就是len(s1)len(s2)次,O(nm).
有办法一遍循环就找出最长的子串嘛?
0001000第一行,索引为3,0。
第二行的时候如果4,1是1,就判断3,0是否为1,为1就把3,0加1。
第二行的时候如果5,2是1,就判断4,1是否为1,是1就加1,再就判断3,0是否为1,为1就把3,0加1 。
上面的方法是个递归问题,不好。
最后在矩阵中找到最大的元素,从它开始就能写出最长的子串了。
但是这个不好算,因为是逆推的,改为顺推。
顺推的意思,就是如果找到一个就看前一个的数字是几,然后在它的基础上加1。
s1 = "abcdefg" s2 = "defabcd" s2 = "defabcdoabcdeftw" s3 = "1234a" s4 = "5678" s5 = "abcdd" def findit(str1,str2): matrix = [] #从x轴或者y轴取都可以,选择x轴,xmax和xindex xmax = 0 xindex = 0 for i,x in enumerate(str2): matrix.append([]) for j,y in enumerate(str1): if x!=y:#若两个字符不相等 matrix[i].append(0) else: if i==0 or j ==0:#两个字符相等,有字符在边上的 matrix[i].append(1) else:#不在边上 matrix[i].append(matrix[i-1][j-1]+1) if matrix[i][j]>xmax:#判断当前加入的值和记录的最大值比较 xmax = matrix[i][j]#记录最大值,用于下次比较 xindex = j#记录当前值的x轴偏移量,和str1[xindex+1-xmax:xindex+1匹配] xindex+=1#只是为了计算的需求才+1,和str1[xindex-xmax:xindex]匹配 #return str1[xindex+1-xmax:xindex+1] return str1[xindex - xmax:xindex] print(findit(s1,s2)) print(findit(s1,s3)) print(findit(s1,s4)) print(findit(s1,s5)) s1 = " abcdefg " s2 = "304abcdd" print(findit(s1,s5)) 结果为: abcdef a abcd abcd
方法二:
可不可以这样思考?
字符串都是连续的字符,所以才有了下面的思路。
思路一:
第一轮
从s1中依次取1个字符,在s2中查找,看是否能够找到子串。
如果没有一个字符在s2中找到,说明就没有公共子串,直接退出。如果找到了至少一个公共子串,则很有可能还有更长的公共子串,可以进入下一轮。
第二轮
然后从s1中取连续的2个字符,在s2中查找,看看能够找到公共的子串。如果没有找到,说明最大公共子串就是上一轮的随便的哪一个就行了。如果找到至少一个,则说明公共子串可能还可以再长一些。可以进入下一轮。
改进,其实只要找到第一轮的公共子串的索引,最长公共子串也就是从它开始的,所以以后的轮次都从这些索引位置开始,可以减少比较的次数。
思路二:
既然是求最大子串,我先看s1全长作为子串。
在s2中搜索,是否返回正常的index, 正常就找到了最长的子串。
没有找到,把s1按照length-1取多个子串。
在s2中搜索,是否能返回正常的index。
注意:
不要一次把s1的所有子串生成,用不了,也不要从最短开始,因为题目要最长的。
但是也要注意,万一他们的公共子串就只有一个字符,或者很少字符的,思路一就会占优势。
s1 = "abcdefg" s2 = "defaabcdoabcdeftw" s3 = "1234a" def findit(str1,str2): count = 0#看看效率,计数 length = len(str1) for sublen in range(length,0,-1): for start in range(0,length - sublen +1): substr = str1[start:start+sublen] count+=1 if str2.find(substr)>-1:#found print("count={},substrlen={}".format(count,sublen)) return substr print(findit(s1,s2)) print(findit(s1,s3)) 结果为: count=2,substrlen=6 abcdef count=22,substrlen=1 a