base64
解法一
暴力求解法:1、字符串每3步一切,得到目标字符串target,将其放入列表
2、遍历列表,按个将target转为字节序列
2、正常情况:字节序列前补零得到24个字节序列,6步一切得到4个子节序列b_character
3、尾部特殊情况:字节序列target不足3个字符,字节序列前补零,每缺一个字符,后面补8个零,最多补16个零,然后6补一切,得到字节序列b_character
4、循环处理b_character,正常4次循环;尾部特殊情况,target长度为2,切3次,避开补的零;target长度为2,切2次,避开补的零。
5、’=‘号追加,target=2,补一个’=’,target=1,补两个’=‘
缺点:
多次遍历、多个列表和字符串,效率极低
import string
base64 = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+' + '/'
# 得到base64字符串
word = '''Man is distinguished, not only by his reason, but by this singular passion
from other animals, which is a lust of the mind, that by a perseverance of delight in
the continued and indefatigable generation of knowledge, exceeds the short vehemence
of any carnal pleasure'''
start = 0
step = 3
stop = start + step
target_lst = []
while True:
target = word[start:stop] # 每三位切割字符串
if len(target) == 0: # 如果没切到,直接终止
break
target_lst.append(target) # 将得到的字符串依次放入列表,
start += step
stop = start + step
if len(target) < 3: # 如果切割的字符串长度小于3,得到全部字符串,终止循环
break
# print(target_lst,'1111')
str_target = '' # 连结整个base64解码后的字符串
for i in target_lst: # 遍历列表,按个对字符串进行处理
int_target = int.from_bytes(i.encode(),'big') # 得到字符串的十进制
length = len(i) # 取字符串长度,判断长度,小于三进行特殊处理
# print(int_target,"22222")
if length == 3: # 字符串长度等于3,前补零,得到目标二进制
b_character = '{:024b}'.format(int_target)
# print(b_character,"33333")
elif length == 2: # 字符串长度等于2,前后补零,得到目标二进制
b_character = '{:016b}'.format(int_target) + 8 * '0'
elif length == 1: # 同上
b_character = '{:08b}'.format(int_target) + 16 * '0'
start = 0
step = 6
stop = start + step
# print(length,'4444')
for _ in range(length+1): # 计数,24为转为4个6位,如果length<3,只能切2次或者3次
# 正好避免切补零的部分
index = int(b_character[start:stop],2) # int函数,将二进制字符串转为十进制
str_target += base64[index] # base64中,索引查找求得的十进制,即为base解码
start += step
stop = start + step
else: # 最后判断,如果目标字符串的长度小于3,补’=‘
if length < 3:
while length: # 长度2,补1个’=‘,长度1,补2个'='
str_target += '=' # 目标字符串补'='
length += 1
if length == 3: length等于3,循环终止
break
print(str_target)
解法二
1、判断字符串能否被3整除,整除不处理,未整除,余数为1,补2个null,余数为2,补1个null,因为null在内存中是0
2、null不可见字符,使用十六进制的字节表示方式’\x00’
3、3步一切,放入目标字典(优化点,步需要放入字典,而是取一个字符串,处理一次)
4、位与63(二进制111111),技巧:n位数字位与n位的二进制1,得到的还是原数字。位与0,得0.
5、将目标字节序列右移6位,得到下一次的目标字节序列
6、从后往前且,先切的往后方
7、用切片将补的位置全部换成’='号(优化点,碰见特殊字符串单独处理,最后再连在一起)
优点:
1、直接补齐字符串,不用特殊补零
2、位与、右移,不用转二进制,提高切割效率
缺点:
1、将切好的字符串放入列表,遍历处理
2、字符串拼好之后,特殊情况:切片生成新的字符串
3、3个大容器,内存开辟空间过大,效率低
import string
base64 = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+' + '/'
word = '''Man is distinguished, not only by his reason, but by this singular passion
from other animals, which is a lust of the mind, that by a perseverance of delight in
the continued and indefatigable generation of knowledge, exceeds the short vehemence
of any carnal pleasure'''
target_lst = [] # 存放目标字符串
length = len(word)
div,mod = divmod(length,3) # div为循环次数,mod用来判断字符串不足3个,做特殊处理
# print(div,mod)
for i in range(div): # 通过步长,得到目标字符串,并且追加到目标列表中
target_lst.append(word[3 * i:3 * (i + 1)]) # 此处可优化,不需要放入列表,只需要每三位转一下,可以提高运行效率
if mod != 0: # 特殊情况,mod=1,字符串一个,需要补两个null,mod=2,需要补一个null
target_lst.append(word[-mod:] + (3 - mod) * '\x00') # null不可见,'\x00'十六进制得到
# print(target_lst)
word_base64 = '' # 用于连结解码后的字符串
for val in target_lst: # 遍历目标列表,按个处理
code = val.encode()
num = int.from_bytes(code,'big') # 得到字符串的十进制表达
word = '' # 连结每次解码后的字符
while num: # num不为零进入,为零退出
index = num & 63 # 核心:63的二进制为111111,位与得到后六位(技巧:与1位与得到原数)
word = base64[index] + word # 从后向前切,切到的值向后追加
num = num >> 6 # 向右位移6位,得到下一次目标字节序列,依次循环
word_base64 += word # 得到目标解码字符串
if mod != 0: # 尾部特殊情况处理
miss = 3 - mod # 补一个null,0~-1 + ’=‘,补两个null,0~-2 + 2个"="
word_base64 = word_base64[:-miss] + miss * '=' # 此处可优化,再次生成一个字符串,占用过多内存
# lst = list(word_base64)
# lst[-miss:] = miss *'='
# word_base64 = ''.join(lst)
print(word_base64)
cache过期清除
解法一
1、模仿源代码:make_key函数专门用来生成key,连接args和kwargs,以及里面元素的数据类型
2、得到被装饰函数的运行时间,使用二元组保存在value中,value[0]为值,value[1]为时间对象
3、如果delta时间超过10,列表中追加k。之后遍历列表,删除cache中的key
3、设定哨兵sentiel,get字典key,返回值不为哨兵,直接返回value。返回值为哨兵,说明cache中无此kv对,返回被装饰函数的返回值,得到时间对象,封装成二元组作为value,创建kv对
4、最后返回value[0]
5、设置哨兵sentinel,解决函数返回值为0,cache_get的值为0无法进入条件的问题。
缺点:
1、时间设定死了,无法更改
2、每次调用都要遍历字典,时间复杂度O(n),效率极低
import datetime,functools,time
def make_key(key_tup,kwargs,typed=False):
key = ()
kw_key = ()
if args: # 遍历args元组,连接赋值给key
key += key_tup
if kwargs: # 字典不为空
kw_key += tuple(kwargs) # 遍历字典的keys,转为tuple
kw_key += tuple(kwargs.values()) # 遍历字典的values,转为tuple
print(kw_key) # 打印查看kw_key
if key == kw_key: # 两者有可能相等?
pass
else:
key += kw_key # 连接赋值给key
if typed: # 类型不为False
key += tuple(type(i) for i in args) # (新技巧:生成器得数据类型,tuple迭代)
# 将args中的元素遍历,type()得到数据类型并连接。
if kwargs: # 字典不为空
key += (type(i) for v in kwargs.values()) # 遍历字典,得到数据类型
elif len(key) == 1 : # 特殊情况,key长度位1,直接返回元组里面的第一个值
return key[0]
return key
def cache(fn):
cache = {} # 缓存字典,闭包不消亡
cache_get = cache.get # 将字典get方法赋值(源代码习得新技巧)
sentienl = object() # 哨兵,object(),祖宗对象(源代码习得新技巧)
@functools.wraps(fn)
def wrapper(*args,**kwargs):
key = make_key(args,kwargs,typed=False) # 生成字典key
lst = [] # 每次进入需要空列表,放删除的key,字典视图对象,遍历中不能改变size
for k,v in cache.items(): # 循环需要放在此处,调用函数就遍历,发现超过时间的就删除(缺点:每次调用都遍历,时间复杂度O(n),效率极低)
delta = (datetime.datetime.now() - v[1]).total_seconds()
# 得到时间差timedelta对象
if delta > 10: # 固定死,差值大于10秒,将删除的k追加到lst中
lst.append(k)
print(lst)
for key in lst: # 遍历列表,删除cache中的key
del cache[key] # 集合、字典在遍历的时候不能改变size
result = cache_get(key,sentinel) # 通过key取值
if result is not sentinel: # 如果不为哨兵,说明cache字典有key,有value
return result[0] # result二元组,0为值,1为datetime.datetime对象
else: # 为哨兵,说明cache中无key
start = datetime.datetime.now() # 生成时间
result = fn(*args,**kwargs) # fn函数调用
cache[key] = (result,start) # cache创建kv对
return result,cache # 查看cache结果
return wrapper
解法二
1、函数的形参个数、位置都是固定的,得到函数的参数,组成前置的key(元组)
2、如果函数参数全有缺省值,则将缺省值按照形参顺序依次连接成key
3、进入make_key函数,判断实参是否在key中,在则不处理,不在则连接成key
4、此时的key元素为形参名、缺省值和实参,解决传入实参和缺省值一样的问题。
5、实参关键字传参位置打乱,但是形参顺序是固定的,只需要判断值在不在key里面即可
6、将duration柯里化,可以设置duration的时长,决定删除时间
缺点:
1、每次进入遍历字典,效率低下
2、可以将前置key的生成放在make_key中
3、没有实现(4,5) 和(5,4)一样的功能
优化:
给cache一个时间,调用之后再定点清空cache
import datetime,functools,time,inspect
def make_key(key_tup,kwargs,typed=False):
# if key_tup:
# key += key_tup
if kwargs: # 如果字典不为空
lst = []
for _,v in kwargs.items(): # 遍历字典的value
if v not in key_tup: # 判断其是否在key_tup中,不在,则与key_tup连结,解决传入一个位置参数
key_tup += (v,) # 修正,可以直接和(v,)相连,(v,)为单个元组的表达方式,
# lst.append(v) # 和一个关键字传参的问题
# 因为v不能直接和tuple连结,所有使用了一个lst
# key_tup += tuple(lst)
if typed:
key_tup += tuple(type(i) for i in key_tup)
key = key_tup
print(key)
if len(key) == 1 :
return key[0]
return key
def cache1(duration=100):
def cache_inner(fn):
cache = {}
cache_get = cache.get
sentinel = object()
@functools.wraps(fn)
def wrapper(*args,**kwargs):
nonlocal hits,misses
key_tup = ()
key_tup += tuple(inspect.signature(wrapper).parameters.keys())
# 获得函数签名的parameters中的key,即形参的变量名,与空元组连结,组成前置的key
# 优化点:可以将其移到make_key函数中
if fn.__defaults__: # 判断fn的缺省值是否为非空,非空则将缺省值与key_tup连结,组成key
key_tup += fn.__defaults__
else: # 如果为空,则将传入的实参与key_tup连结,组成key
key_tup += args
key = make_key(key_tup,kwargs,typed=False)
# 解决kwargs的问题
lst = [] # 每次进入就遍历,所以是空列表就可以
for k,v in cache.items(): # 这个循环需要放在前面,进入字典就遍历,发现超过时间的就删除
delta = (datetime.datetime.now() - v[1]).total_seconds()
if delta > duration: # 遍历字典,查询v里面时间,并得到delta对象
lst.append(k) # duration柯里化成函数的参数,可以设置删除时间
# print(lst)
result = cache_get(key,sentinel)
for key in lst:
del cache[key] # 集合在遍历的时候也不能改变size
if result is not sentinel:
return result[0] # result是一个二元组,索引0为值,索引1为时间
else:
start = datetime.datetime.now()
result = fn(*args,**kwargs)
cache[key] = (result,start)
return result,cache
return wrapper
return cache_inner