算法面试其他篇
目录:
1.1 python模拟LRU(Least recently used,最近最少使用)
定义:算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
核心:
1. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
2. 当链表满的时候,将链表尾部的数据丢弃。
1、实现原理
1)使用三个数据标识这个缓存系统
self.cache = {} # cache模拟所有缓存系统中数据 key: val
self.keys = [] # keys只存放cache字典中的 key
self.size = size # size 来定义这个缓存系统最大容量
2)模拟数据加入缓存(set)
1. 如果缓存为达到最大长度,新数据key插入到 keys列表头部,将key:val存入字典
2. 如果达到最大长度,先删除最后列表keys最后一个元素,将这个key:val 从cache字典中删除
3)模拟获取缓存数据(get)
1. 判断获取的key是否在 cache字典中,如果在就从列表keys中先删除原有key,然后将key插入到列表最前面,并返回val
2. 如果key不在cache字典中,直接返回None即可
#!/usr/bin/env python # -*- coding:utf-8 -*- class LRUcache: def __init__(self, size=3): self.cache = {} self.keys = [] self.size = size def get(self, key): if key in self.cache: self.keys.remove(key) self.keys.insert(0, key) return self.cache[key] else: return None def set(self, key, value): if key in self.cache: self.keys.remove(key) self.keys.insert(0, key) self.cache[key] = value elif len(self.keys) == self.size: old = self.keys.pop() self.cache.pop(old) self.keys.insert(0, key) self.cache[key] = value else: self.keys.insert(0, key) self.cache[key] = value if __name__ == '__main__': test = LRUcache() test.set('a', 1) test.set('b', 2) test.set('c', 3) test.set('d', 4) print test.keys # ['d', 'c', 'b'] print test.cache # {'c': 3, 'b': 2, 'd': 4} print(test.get('a')) # None 当d=4加入缓存是,缓存到达上线3,所以把a从缓存中移除了 print(test.get('b')) # 2 print(test.get('c')) # 3 print(test.get('d')) # 4
1.2 遍历文件夹
#!/usr/bin/python # -*- coding: utf-8 -*- import os def gci(filepath): files = os.listdir(filepath) # 遍历filepath下所有文件,包括子目录 for fi in files: fi_d = os.path.join(filepath, fi) if os.path.isdir(fi_d): gci(fi_d) else: print(os.path.join(filepath, fi_d)) # 递归遍历/root目录下所有文件 gci('C:Go')
1.== 比较的是两个对象的内容是否相等,即内存地址可以不一样,内容一样就可以了。
2.is 比较的是两个实例对象是不是完全相同,它们是不是同一个对象,占用的内存地址是否相同。
1.4 python读取超大文件
1)普通读文件方法弊端分析
1.with 上下文管理器会自动关闭打开的文件描述符,在迭代文件对象时,内容是一行一行返回的,不会占用太多内存
2. 如果python读取文件如果被读取的文件里,根本就没有任何换行符,将会变成一个非常巨大的字符串对象,占用大量内存。
#! /usr/bin/env python # -*- coding: utf-8 -*- def read_file(fname): with open(fname) as file: for line in file: print(line.strip(' '),) path = r'C:aaalutingedc-backendaaa.py' read_file(path)
2)读取大文件正确方式
1. 我们使用了一个 while 循环来读取文件内容,每次最多读取 8kb 大小
2. 这样可以避免之前需要拼接一个巨大字符串的过程,把内存占用降低非常多。
#!/usr/bin/python # -*- coding: utf-8 -*- def read_big_file_v(fname): block_size = 1024 * 8 with open(fname,encoding="utf8") as fp: while True: chunk = fp.read(block_size) # 当文件没有更多内容时,read 调用将会返回空字符串 '' if not chunk: break print(chunk) path = r'C:aaalutingedc-backend ttt.py' read_big_file_v(path)
1.5 手写三级装饰器
1、手写普通装饰器
#!/usr/bin/python # -*- coding: utf-8 -*- def outer_wrapper(func): def wrapper(*args, **kwargs): print('---->') res = func(*args, **kwargs) return wrapper @outer_wrapper def home(): print("welcome to home page") return "from home" home()
2、三级装饰器
#!/usr/bin/python # -*- coding: utf-8 -*- def auth(auth_type): def outer_wrapper(func): def wrapper(*args, **kwargs): print("---->", auth_type) res = func(*args, **kwargs) return wrapper return outer_wrapper @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" home()
3、计算函数执行时间装饰器
#! /usr/bin/env pythonf # -*- coding: utf-8 -*- import time def timer(func): #timer(test1) func=test1 def deco(*args,**kwargs): start_time = time.time() func(*args,**kwargs) #run test1 stop_time = time.time() print("running time is %s"%(stop_time-start_time)) return deco @timer # test1=timer(test1) def test1(): time.sleep(3) print("in the test1") test1()
1.6 使用yield生成器生成斐波拉契函数
#!/usr/bin/python # -*- coding: utf-8 -*- def fib(max_num): a,b = 1,1 while a < max_num: yield b a,b=b,a+b g = fib(10) #生成一个生成器:[1,2, 3, 5, 8, 13] print(g.__next__()) #第一次调用返回:1 print(list(g)) #把剩下元素变成列表:[2, 3, 5, 8, 13]
1.7 单例模式
1、单例模式原理及作用
1、单例模式:永远用一个对象得实例,避免新建太多实例浪费资源
2、实质:使用__new__方法新建类对象时先判断是否已经建立过,如果建过就使用已有的对象
3、使用场景:如果每个对象内部封装的值都相同就可以用单例模式
2、单例模式两种写法
class Foo(object): instance = None def __init__(self): self.name = 'alex' def __new__(cls, *args, **kwargs): if Foo.instance: return Foo.instance else: Foo.instance = object.__new__(cls,*args,**kwargs) return Foo.instance obj1 = Foo() # obj1和obj2获取的就是__new__方法返回的内容 obj2 = Foo() print(obj1,obj2) # 运行结果: <__main__.Foo object at 0x00D3B450> <__main__.Foo object at 0x00D3B450> # 运行结果说明: # 这可以看到我们新建的两个Foo()对象内存地址相同,说明使用的•同一个类,没有重复建立类
#!/usr/bin/python # -*- coding: utf-8 -*- class Singleton(object): def __new__(cls, *args, **kwargs): #new方法最后返回的是一个实例 if not hasattr(cls, "_instance"): #如果没有这个字段就调用父类创建 cls._instance = super(Singleton, cls).__new__(cls) return cls._instance #永远返回的就是第一次创建的对象 obj1 = Singleton() obj2= Singleton() print(obj1,obj2)
1.8 python高阶函数
更多详见:https://www.cnblogs.com/xiaonq/p/7898817.html#i2
1、三元运算 & lambda
name = 'Tom' if 1 == 1 else 'fly' print(name) # 运行结果: Tom
2、lambda使用
f = lambda x:x if x % 2 != 0 else x + 100 print(f(10)) # 110 f = lambda x,y,z:x+y+z print(f(1,2,3)) # 6
3、 利用 filter、lambda表达式 获取l1中元素小于33的所有元素 l1 = [11, 22, 33, 44, 55]
l1= [11,22,33,44,55] a = filter(lambda x: x<33, l1) print(list(a))
4、利用map,lambda表达式将所有偶数元素加100
l1= [11,22,33,44,55] ret = map(lambda x:x if x % 2 != 0 else x + 100,l1) print(list(ret)) # 运行结果: [11, 122, 33, 144, 55]
5、总结:filter()和map()函数区别
1. Filter函数用于对序列的过滤操作,过滤出需要的结果,一次性返回他的过滤设置于的是条件
2. Map函数是对序列根据设定条件进行操作后返回他设置的是操作方法,无论怎样都会返回结果
7、使用reduce求和
from functools import reduce # py3 print(reduce(lambda x, y: x + y, [ 1, 2, 3, 4])) # 10
'''使用reduce将字符串反转''' s = 'Hello World' from functools import reduce result = reduce(lambda x,y:y+x,s) # 1、第一次:x=H,y=e => y+x = eH # 2、第二次:x=l,y=eH => y+x = leH # 3、第三次:x=l,y=leH => y+x = lleH print( result ) # dlroW olleH
8、sorted排序
students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)] print( sorted(students, key=lambda s: s[2], reverse=False) ) # 按年龄排序 # 结果:[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
d = {'k1':1, 'k3': 3, 'k2':2} # d.items() = [('k1', 1), ('k3', 3), ('k2', 2)] a = sorted(d.items(), key=lambda x: x[1]) print(a) # [('k1', 1), ('k2', 2), ('k3', 3)]
1.9 python求阶乘 & 青蛙跳问题
1、求4的阶乘
def test(n): if n == 1: return 1 else: res = n*test(n-1) return res print(test(4)) # 24
2、青蛙跳问题
#! /usr/bin/env python # -*- coding: utf-8 -*- import sys sys.setrecursionlimit(1000000000) #设置系统最大递归深度 def fib(n): if n <= 2: return n else: return fib(n-1) + fib(n-2) print(fib(4)) # 5
#! /usr/bin/env python # -*- coding: utf-8 -*- import sys sys.setrecursionlimit(1000000000) #设置系统最大递归深度 def fib(n): if n <= 2: return n elif n == 3: return 4 else: return fib(n-1) + fib(n-2) + fib(n-3) print(fib(4)) # 7
#! /usr/bin/env python # -*- coding: utf-8 -*- import sys sys.setrecursionlimit(1000000000) #设置系统最大递归深度 def fib(n): if n <= 2: return n else: return 2 * fib(n - 1) print(fib(4)) # 8
1.10 瓶盖换酒问题
问题:2元一瓶酒,2个空瓶换一瓶,4个瓶盖换一瓶 问10块钱买几瓶酒??
class Change: def __init__(self,money): self.money=money # 总共的钱 self.beer=money//2 # 买到酒的数量(第一次用钱买酒) self.cap=0 # 瓶盖数量 self.bottle=0 # 空瓶数量 self.count=0 # 总共和了多少瓶酒 def ping(self): new_beer=self.bottle//2 # 当前空瓶可换酒的数量 self.beer+=new_beer # 更新酒的数量 self.bottle-=(new_beer*2) # 将已使用的空瓶去除 def gai(self): new_beer=self.cap//4 # 当前瓶盖换酒的数量 self.beer+=new_beer # 更新酒的数量 self.cap-=(new_beer*4) # 将已使用的瓶盖去除 def drink(self): self.bottle+=self.beer # 最终空瓶 = 酒的数量+当前空瓶数量 self.cap+=self.beer # 瓶盖数量 = 酒的数量+当前瓶数量 self.count+=self.beer # 喝酒数量 = 酒的数量+已经喝的数量 self.beer=0 # 喝完后把酒的数量重置为0 def run(self): while self.beer>0 or self.bottle>=2 or self.cap>=4: self.drink() # 喝酒 self.ping() # 空瓶换酒 self.gai() # 瓶盖换酒 return '喝了%d瓶酒,剩余%d个瓶子,剩余%d个盖子'%(self.count,self.bottle,self.cap) person=Change(10) print(person.run()) # 喝了15瓶酒,剩余1个瓶子,剩余3个盖子
1.11 [lambda x: x*i for i in range(4)] 本质解析
1、[lambda x: x*i for i in range(4)] 运行结果
# -*- coding: utf-8 -*- fun = [lambda x: x*i for i in range(4)] for item in fun: print(item(1), end=' ') # 预计结果为:0, 1, 2, 3 # 实际输出为:3, 3, 3, 3
#! /usr/bin/env python # -*- coding: utf-8 -*- fun = [lambda x, i=i: x*i for i in range(4)] for item in fun: print(item(1), end=' ') # 运行结果:0 1 2 3
2、原因分析
注:python解释器查找变量时,会按照顺序依次查找:局部作用域--->嵌套作用域--->全局作用域--->内建作用域
#! /usr/bin/env python # -*- coding: utf-8 -*- def func(): tmp = [] for i in range(4): print('外层:',i) # 0 1 2 3 def lambda_(x): print('内存i:',i) # 3 return x * i tmp.append(lambda_) return tmp fl = func() fl[0](1) '''1. 运行结果如下:''' # 外层: 0 # 外层: 1 # 外层: 2 # 外层: 3 # 内存i: 3 '''2. 原因分析''' # 1. 四次循环中外层函数命名空间中的 i 从 0-->1-->2-->3 最后固定为3 # 2. 内嵌函数lambda_中因为没有定义 i ,从外层函数获取的值都为3 # 3. 导致得不到预计输出结果:0,1,2,3 只能得到 3, 3, 3, 3
1.12 ip地址转换
1、求当前ip地址的下一个地址
1) socket.inet_aton: 将IPv4点分地址转换成32位进制数
2) struct.unpack('!I': 将32位数转换成,十位无符号整形
3) struct.pack('!I':将十位数字转换成 32位进制数
4) socket.inet_ntoa: 把32位进制数转换成ip 127.0.1.0
import socket import struct if __name__ == '__main__': ip = '127.0.0.255' int_ip = struct.unpack('!I', socket.inet_aton(ip))[0] # 2130706687 (int) str_ip = socket.inet_ntoa(struct.pack('!I', int_ip+1 )) # 127.0.1.0
#! /usr/bin/env python # -*- coding: utf-8 -*- import socket import struct if __name__ == '__main__': ip = '127.0.0.255' '''1. 将ip转成数字''' # 1.1 socket.inet_aton: 将IPv4点分地址转换成32位进制数 ip_to_32dec = socket.inet_aton(ip) # b'x7fx00x00xff' # 1.2 struct.unpack('!I': 将32位数转换成,十位无符号整形 dec32_to_10 = struct.unpack('!I', ip_to_32dec)[0] # 2130706687 '''2. 将数字转换成ip''' # 2.1 struct.pack('!I':将十位数字转换成 32位进制数 dec10_to_32 = struct.pack('!I', 2130706688) # b'x7fx00x01x00' # 2.2 socket.inet_ntoa: 把32位进制数转换成ip 127.0.1.0 f_ip = socket.inet_ntoa(dec10_to_32) print(f_ip) # 127.0.1.0
1.13 统计文章中单词出现次数并进行排序
1、统计指定文件中单词出现数量,并找到出现次数最多的前无个
import io import re class Counter: def __init__(self, path): self.mapping = dict() with io.open(path, encoding="utf-8") as f: data = f.read() words = [s.lower() for s in re.findall("w+", data)] print(words) # ['version', '3', 'services', .... ] for word in words: self.mapping[word] = self.mapping.get(word, 0) + 1 def most_common(self, n): return sorted(self.mapping.items(), key=lambda item: item[1], reverse=True)[:n] if __name__ == '__main__': pt = r'C:aaalutingedc-backenddocker-compose.yml' most_common_5 = Counter(pt).most_common(5) print(most_common_5) # [('mysql', 5), ('root', 3), ('db', 3), ('environment', 2), ('3306', 2)]
1111111111111111