对于一个函数或未知的方法不知道下面有哪些可调用的属性时,用dir(函数/方法)即可查看
1、给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
输入:nums = [3,2,4], target = 6
输出:[1,2]
# class Solution_1(object):
# def twoSum(self, nums, target):
# return [[nums.index(i),nums.index(target-i)] for i in nums if (target-i) in nums and (target-i) != i]
class Solution_2(object):
def twoSum(self, nums, target):
result = []
seen = set()
for i, num in enumerate(nums):
if target - num in seen:
result.append([nums.index(target - num), i])
seen.add(num)
return result
print(Solution_2().twoSum([1, 2, 3, 4, 5], 6))
2、给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
class Solution(object):
def addTwoNumbers(self, l1, l2):
res = []
result = 0
for i in range(len(l1)):
result += l1[i] * 10**(i)
for i in range(len(l2)):
result += l2[i] * 10**(i)
for i in range(1,(len(str(result)))+1):
res.append(str(result)[-i])
return res
print(Solution().addTwoNumbers([9,9,9,9,9,9,9], [9,9,9,9]))
3、给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
eg:
知识点:前序遍历:根节点 -> 左子树 -> 右子树;中序遍历:左子树 -> 根节点 -> 右子树;后序遍历:左子树 -> 右子树 -> 根节点
前序遍历:1, 2, 4, 5, 3, 6, 7;中序遍历:4, 2, 5, 1, 6, 3, 7;后序遍历:4, 5, 2, 6, 7, 3, 1
思路很简单,所有遍历都可从根root开始,如前序遍历规则为根—左—右:先遍历根:1__2__3__(有子树有空格),再遍历左:245(无子树不用空格),合并后就是1245__3__,最后遍历右:367,合并就是1245367。
中序遍历规则为左—根—右:先遍历根:2__1__3__,再遍历左:425,合并后就是4251__3__,最后遍历右:637,合并就是4251637。
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def buildTree(preorder, inorder):
if not preorder or not inorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
# 在中序遍历中找到根节点的索引
root_index = inorder.index(root_val)
# 递归构建左右子树
root.left = buildTree(preorder[1:1+root_index], inorder[:root_index])
root.right = buildTree(preorder[1+root_index:], inorder[root_index+1:])
return root
# 打印树结构
def print_tree(node, level=0, label='root'):
print(' ' * (level * 4) + label + ':', node.val)
if node.left:
print_tree(node.left, level + 1, 'left')
if node.right:
print_tree(node.right, level + 1, 'right')
print_tree(buildTree([1, 2, 4, 5, 3, 6, 7], [4, 2, 5, 1, 6, 3, 7]))
4、给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是'wke',长度为3.
class Solution(object):
def lengthOfLongestSubstring(self, s):
result = pd.DataFrame()
for i in range(len(s)):
res = []
res.append(s[i])
for j in range(i+1, len(s)):
if s[j] not in res:
res.append(s[j])
num = len(res)
else:
num = len(res)
resu = pd.DataFrame({'name':''.join(res), 'len':num},index=[0])
result = pd.concat([result,resu])
break
resu = pd.DataFrame({'name':''.join(res), 'len':num},index=[0])
result = pd.concat([result,resu])
result.sort_values(by='len', ascending=False, inplace=True)
return result.head(1)
Solution().lengthOfLongestSubstring('pwwkew') # hdhedwhi
5、给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (m+n))
。
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2): # O(nlogn)
nums1.extend(nums2) # nums1 + nums2
nums1.sort(reverse=False)
if len(nums1) % 2 == 0:
return (nums1[len(nums1)//2 - 1] + nums1[len(nums1)//2]) / 2
else:
return nums1[len(nums1)//2]
def findMedianSortedArrays_1(self, nums1, nums2): # O(log(min(m, n)))
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
left, right = 0, m
total_len = (m + n + 1) // 2
while left < right:
i = (left + right) // 2
j = total_len - i
if i < m and nums2[j-1] > nums1[i]:
left = i + 1
else:
right = i
i = left
j = total_len - i
nums1_left_max = float('-inf') if i == 0 else nums1[i-1]
nums1_right_min = float('inf') if i == m else nums1[i]
nums2_left_max = float('-inf') if j == 0 else nums2[j-1]
nums2_right_min = float('inf') if j == n else nums2[j]
if (m + n) % 2 == 0:
return (max(nums1_left_max, nums2_left_max) + min(nums1_right_min, nums2_right_min)) / 2
else:
return max(nums1_left_max, nums2_left_max)
print(Solution().findMedianSortedArrays([1,4,6],[2,5,8,10]))
6、给定一个字符串 s
,找到 s
中最长的回文子串(如果字符串的反序与原始字符串相同,则该字符串称为回文字符串)。
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
def longestPalindrome(s):
n = len(s)
dp = [[False] * n for _ in range(n)]
start = 0
max_len = 1
# 初始化长度为1和2的回文子串
for i in range(n):
dp[i][i] = True
if i < n - 1 and s[i] == s[i + 1]:
dp[i][i + 1] = True
start = i
max_len = 2
# 动态规划遍历
# 遍历子串长度大于2的情况
for len_ in range(3, n + 1):
for i in range(n - len_ + 1):
j = i + len_ - 1
if s[i] == s[j] and dp[i + 1][j - 1]:
dp[i][j] = True
start = i
max_len = len_
return s[start:start + max_len]
s = "bsabasda"
longestPalindrome(s)
Python中常用的数据类型包括整数、浮点数、字符串、列表、元组、字典等。
Python中的控制流语句包括if语句、for循环和while循环,用于实现条件判断和循环操作。
函数是一段封装好的代码块,可以重复调用。在Python中定义函数使用关键字def
。
RTSP(Real Time Streaming Protocol)是一种用于在IP网络上传输音频和视频数据的协议。在Python中,我们可以使用第三方库来实现RTSP视频流的处理和播放。
安装相应的依赖库。其中最常用的库是OpenCV和FFmpeg:
pip install opencv-python
pip install ffmpeg-python
播放RTSP视频流:
import cv2
"""要确定一个视频流是否为RTSP流,可以通过检查流的URL协议:RTSP流通常使用rtsp://作为URL的协议前缀。因此,只需检查流的URL是否以rtsp://开头即可判断是否为RTSP流。
如果想正常使用可以通过一下办法自己搭建一个测试地址:
1.打开 https://rtsp.stream/;
2.拉取到最后,点击Free下的"Get started"填写邮箱;
3.打开邮箱,打开指定的链接后,会给出2个免费的有效地址,每月有2G流量可免费使用。
可通过VLC播放器直接打开播放
"""
rtsp_url = "rtsp://rtspstream:7e536546fadf62359810a7c9a53ad6fe@zephyr.rtsp.stream/pattern"
cap = cv2.VideoCapture(rtsp_url)
while True:
ret, frame = cap.read()
if ret:
cv2.imshow("RTSP Stream", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):# 在代码框里面输入q退出播放
break
cap.release()
cv2.destroyAllWindows()
测试结果:
验证字符串包含:
1、使用in关键字:
s1 = 'hello'
s2 = 'hello world'
s1 in s2
# result: True
2、使用find方法:str1.find(str2):在str2中是否找到字符串str1
s1 = 'hello'
s2 = 'hello world'
s1.find(s2)
# result: -1:找到
3、使用re模块:re.search(s1, s2):在s2中能否找到s1
import re
s1 = 'hello'
s2 = 'hello world'
re.search(s1, s2)
re.findall(s1, s2)
# <re.Match object; span=(0, 5), match='hello'>
4、正则匹配
正则表达式(Regular Expression)是一种用来描述字符串匹配规则的字符序列
.
: 匹配除换行符以外的任意字符。^
: 匹配字符串的开头。$
: 匹配字符串的结尾。*
: 匹配0个或多个前面的字符。+
: 匹配1个或多个前面的字符。?
: 匹配0个或1个前面的字符。{}
: 匹配特定次数的前面的字符。[]
: 匹配括号内的任意一个字符。|
: 匹配多个条件中的任意一个。
(?<=
)是正则表达式中的一个零宽度正后发断言(Lookbehind Assertion),也称为后顾前瞻条件。它用于指定匹配的文本必须出现在某个特定模式之后,但该模式本身不包含在最终的匹配结果中。
(?=)
:是正向前瞻断言,它指定一个位置,该位置后面的字符串必须符合括号内的模式,但这个匹配的内容不会被包含在最终匹配的结果中。
实际上 .* 能匹配无限长的字符。
import re
html = """
<a href="https://www.google.com">Google</a>
<a href="https://www.baidu.com">Baidu</a>
"""
pattern = re.compile(r'(?<=href=").*(?=")')
results = pattern.findall(html)
print("提取到的链接是:", results)
5、用count方法判断是否包含:如果包含出现次数就>0
s = "hello world"
if s.count('a') > 0:
print(s.count('a'))
else:
print("no a")
6、用index()
方法来查找子串在字符串中的位置:
# 使用index()方法查找子串
str1 = "Hello, World!"
sub_str = "World"
# 查找子串在字符串中的位置
try:
index = str1.index(sub_str)
print(f"子串在字符串中的位置为: {index}")
except ValueError:
print("子串不在字符串中")
7、用startswith()
和endswith()
方法判断字符串起始和结尾
str1 = "Hello, World!"
prefix = "Hello"
suffix = "World"
# 判断字符串是否以特定子串起始或结尾
if str1.startswith(prefix):
if str1.endswith(suffix):
输出列表最后一个元素:
1、使用负索引:显示元素
st = [1,2,3]
st[-1]
2、使用pop函数:会显示弹出的最后一个元素并会在原列表中删除
my_list = [1, 2, 3, 4, 5]
my_list.pop()
3、切片:返回的是包含最后一个元素的列表
my_list = [1, 2, 3, 4, 5]
my_list[-1:]
4、使用列表长度:
my_list[len(my_list)-1]
检查文件路径是否存在:
import os
def create_directory(directory_path):
# 检查文件夹路径是否存在
if os.path.exists(directory_path):
# 如果存在则先删除
os.remove(directory_path)
if not os.path.exists(directory_path):
# 如果不存在则创建文件夹
os.makedirs(directory_path)
directory_path = 'path/to/directory'
create_directory(directory_path)
取出文件名中间几位:
在文件处理过程中,需要从文件名中提取特定位置的字符或信息,以便对文件进行分类、重命名或其它操作。
import os
def get_middle_chars_from_filename(filename, start, end):
base = os.path.basename(filename) # 获取文件名部分
middle_chars = base[start:end] # 提取中间字符
return middle_chars
filename = "example_file_20211225.txt"
result = get_middle_chars_from_filename(filename, 13, 19)
print(result)
读取指定行的数据:
1、readline方法:可以用来逐行读取文件中的数据:
# 打开文件
with open('data.txt', 'r') as file:
# 读取文件的第三行数据
for i in range(3):
line = file.readline()
print(line)
2、readlines方法:可以一次性读取所有行的数据,并返回一个包含所有行的列表。从这个列表中找到需要的特定行:
# 打开文件
with open('data.txt', 'r') as file:
# 读取所有行数据
lines = file.readlines()
# 找到第五行数据
print(lines[4])
3、enumerate方法:遍历文件的每一行,并找到需要的特定行:
# 打开文件
with open('data.txt', 'r') as file:
# 遍历文件的每一行
for idx, line in enumerate(file):
# 找到第六行数据
if idx == 5:
print(line)
break
设置服务器代理:
使用requests库、urllib库、http.client库设置代理
计算两个日期之间的天数:
import datetime
print(datetime.date(2022, 1, 1)) # class 'datetime.date' : 2022-01-01
print(datetime.datetime.strptime('2022-01-01', '%Y-%m-%d')) # class 'datetime.datetime : 2022-01-01 00:00:00
print(datetime.date(2022, 1, 1).strftime('%Y-%m-%d')) # class 'str' : 2022-01-01
(datetime.date(2022, 12, 1) - datetime.date(2022, 1, 1)).days # 334
(datetime.datetime.strptime('2022-12-01', '%Y-%m-%d') - datetime.datetime.strptime('2022-01-01', '%Y-%m-%d')).days # 334
考虑闰年的影响。闰年是指可以被4整除但不能被100整除,或者可以被400整除的年份。在Python中,可以使用calendar模块中的isleap()函数来判断一个年份是否是闰年。
# 导入datetime和calendar模块
import datetime; import calendar
# 定义两个日期
date1 = datetime.date(2020, 2, 28); date2 = datetime.date(2021, 2, 28)
# 计算两个日期之间的天数
delta = date2 - date1 # 366
# 考虑闰年的影响
leap_days = sum(calendar.isleap(date1.year + x) for x in range(1,(date2.year - date1.year))) # 0
days = delta.days + leap_days # 366
print(days)
获取昨天的日期(或上一工作日的日期):
1、用datetime中的timedelta模块
import datetime
yesterday = datetime.date.today() - datetime.timedelta(days=1)
while True:
yesterday = yesterday - datetime.timedelta(days=1)
if yesterday.weekday() < 5:
break
print(yesterday)
datetime.date(2018, 1, 1) - datetime.timedelta(days=1)
2、用arrow模块
import arrow
arrow.now().shift(days=-1).format('YYYY-MM-DD')
3、判断是否是节假日还是工作日,用holidays模块:
import holidays
# 创建中国节假日对象
china_holidays = holidays.China()
# 判断日期是否为节假日
date = '2024-04-04'
if date in china_holidays:
print(f'{date} 是节假日')
else:
print(f'{date} 不是节假日')
4、用workalendar模块:
from workalendar.asia import China
# 创建中国节假日对象
cal = China()
# 判断日期是否为调休
date = datetime.date(2023,2,9)
if cal.is_working_day(date):
print(f'{date} 是调休日')
else:
print(f'{date} 不是调休日')
时区转换:
import pytz
from datetime import datetime
utc_now = datetime.utcnow() # 获取当前的UTC时间
utc = pytz.utc
tokyo = pytz.timezone('Asia/Tokyo') # 转换成其他时区
tokyo_now = utc_now.replace(tzinfo=utc).astimezone(tokyo) # 用replace()方法将UTC时间转换成UTC时区时间,并使用astimezone()方法将其转换成东京时区时间
print(tokyo_now)
自动识别不同格式的日期:
用dateutil库中的parser处理:
from dateutil import parser
dates = ["2021-09-10", "10/09/2021", "2021年09月10日"]
for date_str in dates:
date = parser.parse(date_str, fuzzy=True)
print(date)
"""
2021-09-10 00:00:00
2021-10-09 00:00:00
2021-09-10 00:00:00
"""
获取当前日期:
import datetime
datetime.datetime.now().date() # 年-月-日
datetime.datetime(2020, 10, 15).date() # 指定日期的年-月-日
current_date = datetime.datetime.now()
year = current_date.year
month = current_date.month
day = current_date.day
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 带时分秒
解码utf-8:
utf-8是一种最常用的字符编码方式,用来表示Unicode字符。在处理文本数据时,经常需要对utf-8进行解码操作:
# utf-8编码的字节数据
utf8_bytes = b'\xe4\xbd\xa0\xe5\xa5\xbd'
# 解码成字符串
utf8_str = utf8_bytes.decode('utf-8')
print(utf8_str)
数组(list)中的元素赋值:
1、索引赋值 2、切片赋值 3、循环赋值
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr[1] = 'a'
arr[-1:] = [11]
for i in range(3,5):
arr[i] += 10
print(arr)
# [1, 'a', 3, 14, 15, 6, 7, 8, 9, 11]
生成等差数列:
1、使用range函数 2、使用numpy库 3、用列表推导式
list(range(1, 11, 2)) # list
np.arange(1, 11, 2) # class 'numpy.ndarray'
[i for i in range(1, 11, 2)]
数据结构—字典操作:
字典是一种无序、可变、元素以键值对形式存储的数据结构
dict(**kwargs) # 传入关键字参数(可选)
my_dict = dict(name='Alice', age=30, city='New York')
dict(mapping, **kwargs) # 传入映射对象(字典)
mapping = {'name': 'Alice', 'age': 30, 'city': 'New York'}
dict(iterable, **kwargs) # 传入可迭代对象
iterable = [('name', 'Alice'), ('age', 30), ('city', 'New York')]
empty_dict = {} # empty_dict2 = dict()
# 添加元素
my_dict['name'] = 'Alice'
my_dict['age'] = 25
# 删除元素
del my_dict['name'] # del删除指定键的元素
age = my_dict.pop('age') # pop删除并返回指定键的值,键值都会删除
my_dict['age'] = 26 # 直接赋值修改
my_dict.clear() # 清空字典
del my_dict # 删除字典
# 访问元素
age = my_dict['age'] # 键不存在,会抛出KeyError
gender = my_dict.get('gender', 'Unknown') # 用get()方法进行安全访问
dict写入csv文件:
import csv
# 定义字典数据
data = [{"name": "Alice", "age": 25, "city": "New York"},
{"name": "Bob", "age": 30, "city": "Los Angeles"},
{"name": "Charlie", "age": 35, "city": "Chicago"}]
# 指定CSV文件路径
csv_file = "data.csv"
# 定义CSV文件的表头
fields = ["name", "age", "city"]
# 写入数据到CSV文件
with open(csv_file, mode='w', newline='') as file:
writer = csv.DictWriter(file, fieldnames=fields)
# 写入表头
writer.writeheader() # 根据之前创建的Writer对象的设置,生成对应格式的表头并写入CSV文件中
# 写入数据
for row in data:
writer.writerow(row)
当路径中包含空格时,可以使用双引号或原始字符串来表示路径:
file_path = r'C:/Program Files/file.txt' # 使用原始字符串
解压rar文件:
可以用rarfile库来解压RAR文件:
import rarfile
def unrar_file(rar_file_path, extract_dir):
rf = rarfile.RarFile(rar_file_path)
rf.extractall(extract_dir)
rf.close()
rar_file_path = "example.rar"
extract_dir_path = "output"
unrar_file(rar_file_path, extract_dir_path)
去除字符串末尾字符:
1、strip()方法:去除字符串前后的指定字符,默认情况下会去除空格、制表符和换行符。通过传入参数指定需要去除的字符,可以实现去除字符串末尾字符的功能。
str = "Hello World "
new_str = str.strip()
print(new_str) # Hello World
2、rstrip()方法:只能去除字符串末尾的字符(区别不大)
3、切片:
new_str = str[:-4]
原子自增:
原子操作是一种能够确保一段代码在多线程或多进程环境下不会被打断的操作,Value
和Lock
是两个常用的类:
multiprocessing
中的Value
类允许在多进程之间共享内存,并提供了一些原子操作方法,例如value.increment()
:
from multiprocessing import Process, Value
def increment_value(val): # 共享整型变量
for _ in range(1000):
val.value += 1
if __name__ == '__main__':
val = Value('i', 0) # 创建一个整型的共享变量val,初始值为0。
processes = [Process(target=increment_value, args=(val,)) for _ in range(4)] # 创建一个包含4个进程的列表processes
for p in processes: # 循环依次启动4个进程
p.start()
for p in processes: # 循环等待所有进程执行完毕
p.join()
print("Final value:", val.value)
用Lock
来保护对共享资源的访问。通过with lock:
语句,我们确保每次只有一个进程可以访问Value
对象,并执行自增操作。这样就可以避免数据竞争带来的问题。
from multiprocessing import Process, Value, Lock
def increment_value(val, lock):
for _ in range(1000):
with lock:
val.value += 1
if __name__ == '__main__':
val = Value('i', 0)
lock = Lock()
processes = [Process(target=increment_value, args=(val, lock)) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
print("Final value:", val.value)
列表初始化:
a = [] #直接赋值
[i for i in range(1, 4)] # 用列表解析式
list()方法 # list()方法
[0] * 3 # 用*操作符
list1 = [1, 2, 3]; list2 = [4, 5, 6]; my_list = list1 + list2 # 用+操作符
list1.extend(list2) # 用extend()方法
list(x for x in range(1, 4)) # 用生成器
list1.copy() # 用copy()方法
copy.deepcopy(list1) # 用deepcopy()方法
- 列表和元组在Python中是两种不同的数据结构,具有不同的特性,转换时需要注意可能存在的数据丢失或格式不一致的情况。
- 列表是可变的数据结构,元组是不可变的数据结构,转换时需确保数据安全性。
- 转换过程中不会改变原列表或元组的内容,只是将其复制一份到新的数据结构中,原数据结构将保持不变
元组和列表互转:
tuple(my_list) # my_list = [1, 2, 3, 4, 5]
list(my_tuple) # my_tuple = (1, 2, 3, 4, 5)
print(sys.getsizeof(my_list)) # 获取my_list占用的字节数
- 整数类型:int(4字节)、long(8字节)、short(2字节)、byte(1字节)
- 浮点数类型:float(4字节)、double(8字节)
- 字符类型:char(2字节)
- 布尔类型:boolean(1字节)
分割数据:
1、手动分割数据:
# 手动分割数据
train_data = data.iloc[:800, :]
test_data = data.iloc[800:, :]
2、用train_test_split函数:scikit-learn库中的train_test_split函数可以方便地将数据集分割成训练集和测试集:
from sklearn.model_selection import train_test_split
import pandas as pd
data = pd.read_csv('data.csv')
# 使用train_test_split函数分割数据 # 随机种子。可以是一个整数,用于确定划分数据集时的随机性。设置相同的随机种子可以保证每次划分的结果一致。如果不指定random_state,则每次划分的结果都会不同。
train_data, test_data = train_test_split(data, test_size=0.2, random_state=42)
3、用numpy的random.permutation方法:可以随机打乱数据集,再根据指定比例分割成训练集和测试集:
# 随机打乱数据集
data_shuffled = data.reindex(np.random.permutation(data.index))
# 分割数据
train_data = data_shuffled.iloc[:800, :]
test_data = data_shuffled.iloc[800:, :]
元组转字典:
元组用于存储不可变的数据集合,而字典用于存储键值对
1、dict()函数
tuples = [('a', 1), ('b', 2), ('c', 3)] # 定义一个包含2个元组的列表
result = dict(tuples) # 使用dict()函数将元组转换为字典
2、列表推导式
result = {key: value for key, value in tuples} # 使用字典推导式将元组转换为字典
集合是一种无序且不重复的数据结构。集合的特点是元素之间没有顺序关系,且集合中的元素不会重复。
用pop弹出集合元素时,集合是无序的,因此弹出的第一个元素可能是任意一个元素;弹出元素之后,原集合中将会删除这个元素。my_set.pop()
Python中嵌入C语言:
创建一个扩展模块:首先需要编写一个C语言源文件,其中包含我们需要实现的功能。然后通过Python的Distutils库将其编译为扩展模块,生成对应的共享库文件。
在Python代码中使用扩展模块:在Python代码中导入刚刚生成的扩展模块,并调用其中定义的函数来执行对应的操作。
// example.c
#include <Python.h>
// C语言函数实现
static PyObject* add(PyObject* self, PyObject* args) {
int a, b;
// 解析Python参数
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
// 执行计算并返回结果
return Py_BuildValue("i", a + b);
}
// 方法列表
static PyMethodDef methods[] = {
{"add", add, METH_VARARGS, "Add two integers"},
{NULL, NULL, 0, NULL}
};
// 模块初始化函数
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
"example",
NULL,
-1,
methods
};
PyMODINIT_FUNC PyInit_example(void) {
return PyModule_Create(&module);
}
# main.py
import example
# 调用C语言函数
result = example.add(1, 2)
print(result)
子串的应用:
子串通常是原字符串的一个部分,也可以为空串,有很多应用场景:
str = "Hello, World!"
sub_str = "World"
if sub_str in str: # 匹配
print("The substring is found.")
sub_str1 = str[:5]; sub_str2 = str[7:] # 切割
print(sub_str1 + sub_str2) # 连接
index = str.find(sub_str) # 查找
if index != -1:
print("The substring is found at index", index)
new_sub_str = "Python"
new_str = str.replace(sub_str, new_sub_str) # 替换
相等比较(==) 和对象比较( is) 区别:
==
被称为相等比较操作符,用于判断两个对象的值是否相等。当使用==
比较两个对象时,Python会比较它们的值,如果它们的值相等,则返回True,否则返回False
is操作符用于比较两个对象是否是同一个对象,也就是它们的内存地址是否相等。如果两个对象的内存地址相等,则is会返回True,否则返回False
x = 10
y = 10
if x == y:
a = [1, 2, 3]
b = a
if a is b:
不同点:
==
用于比较两个对象的值,而is用于比较两个对象的内存地址.。
==
会调用对象的__eq__
方法进行比较,而is直接比较对象的内存地址。
在比较基本数据类型(如整数、字符串)时,==
和is的行为是相同的。但在比较复杂数据类型(如列表、字典)时,它们的行为可能不同。
何时使用==
和is:
当需要比较两个对象的值时,应该使用==
操作符。
当需要比较两个对象是否是同一个对象时,应该使用is操作符。
在比较字符串、整数等基本数据类型时,通常可以使用==
操作符。但在比较列表、字典等复杂数据类型时,最好使用is操作符
搭建简单的HTTP服务器:
1、SimpleHTTPServer
模块提供了一个简单的HTTP服务器,可以用于快速搭建一个静态文件服务器:
# 导入SimpleHTTPServer模块
import SimpleHTTPServer
import SocketServer
# 设置监听端口为8888
PORT = 8888
# 创建一个简单的HTTP服务器
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler)
# 启动服务器
print "Server started at port", PORT
httpd.serve_forever()
保存为server.py
,在命令行中执行python server.py
即可启动一个HTTP服务器,然后在浏览器中访问http://localhost:8888
即可看到服务器的默认页面。
但目前python3.X没有该模块了,因此只需要在命令行中输入:
python -m http.server 8000
就可以启动一个静态服务器了。
SimpleHTTPServer
模块默认会将当前目录作为根目录,我们可以通过http://localhost:8000
来访问当前目录下的文件。
自定义根目录:如果想要使用自定义的根目录,可以使用BaseHTTPServer
模块中的BaseHTTPRequestHandler
类来重写do_GET
方法,如下所示:
import SimpleHTTPServer
import SocketServer
PORT = 8888
class CustomHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write("Hello, world!")
Handler = CustomHandler
httpd = SocketServer.TCPServer(("", PORT), Handler)
print("Server started at port", PORT)
httpd.serve_forever()
在python3中自定义访问的根目录设置如下:
python -m http.server 8000 --directory C:/Users/59980/Desktop
2、BaseHTTPServer
模块提供了更加底层的HTTP服务器实现,可以用于定制更复杂的HTTP服务器(目前python3.7以上不适用了):
import BaseHTTPServer
PORT = 8888
class CustomHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write("Hello, world!")
try:
httpd = BaseHTTPServer.HTTPServer(("", PORT), CustomHandler)
print("Server started at port", PORT)
httpd.serve_forever()
except KeyboardInterrupt:
print("Server stopped")
保存为server.py
,在命令行中执行python server.py
即可启动一个HTTP服务器,访问http://localhost:8888
将输出Hello, world!
。
BaseHTTPServer
模块中的BaseHTTPRequestHandler
类也提供了处理POST请求的方法do_POST
,可以在其中处理POST请求的逻辑:
import BaseHTTPServer
import cgi
PORT = 8888
class CustomHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_POST(self):
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD':'POST',
'CONTENT_TYPE':self.headers['Content-Type'],
})
self.wfile.write("Hello, " + form['name'].value + "!")
try:
httpd = BaseHTTPServer.HTTPServer(("", PORT), CustomHandler)
print "Server started at port", PORT
httpd.serve_forever()
except KeyboardInterrupt:
print "Server stopped"
数值补齐:
1、字符串补齐:
ljust()
方法将字符串靠左对齐,并用指定的字符(默认为空格)进行填充,使其总长度达到指定值。
rjust()
方法将字符串靠右对齐,并用指定的字符进行填充,使其总长度达到指定值。
center()
方法将字符串居中对齐,并用指定的字符进行填充,使其总长度达到指定值。
s = "Python"
result = s.ljust(10, '-')
print(result) # Python----
result = s.rjust(10, '*')
print(result) # ****Python
result = s.center(10, '+')
print(result) # ++Python++
2、数字补齐:
zfill()
方法在数字的左侧用0进行填充,使其总长度达到指定值。
num = 123
result = str(num).zfill(6)
print(result) # 000123
3、格式化补齐:
s = "Python"
result = "{:<10}".format(s) # 左对齐
print(result)
result = "{:>10}".format(s) # 右对齐
print(result)
result = "{:^10}".format(s) # 居中对齐
print(result)
格式化数字补齐:
num = 123
result = "{:06d}".format(num) # 数字左侧补0,总长度为6
print(result)
获取音频长度:
from pydub import AudioSegment
# 只能读取wav格式音频
audio_file = AudioSegment.from_file("audio.wav")
audio_length = len(audio_file)
print("音频长度为:", audio_length, "毫秒")
还可以使用Python的内置库wave
来获取音频的长度:
import wave
# 只能获取wav音频
with wave.open(r"C:\Users\59980\Downloads\植物大战僵尸背景音乐.wav", "rb") as audio_file:
audio_length = audio_file.getnframes() / audio_file.getframerate()
# 音频的帧数 / 帧速率
print("音频长度为:", audio_length, "秒")
获取指定格式文件路径:
用os模块来实现文件路径查找:筛选出所有以’.xlsx’结尾的文件路径
import os
# 指定文件夹路径
folder_path = 'C:/Users/59980/Desktop/新建文件夹/3月初盲审论文数据修正/实证结果/'
# 获取文件夹下所有文件和目录
files = os.listdir(folder_path)
# 指定文件格式
file_format = '.xlsx'
# 筛选特定格式的文件路径
for file in files:
print(file)
if file.endswith(file_format):
file_path = os.path.join(folder_path, file)
print(file_path)
"""
pearson.docx
pearson.xlsx
C:/Users/59980/Desktop/新建文件夹/3月初盲审论文数据修正/实证结果/pearson.xlsx
"""
除了os模块,还可以使用glob模块来获取指定格式文件的路径。glob模块提供了一个函数glob(),可以用来查找匹配指定模式的文件路径:
import glob
# 指定文件夹路径
folder_path = 'C:/Users/59980/Desktop/新建文件夹/3月初盲审论文数据修正/实证结果/'
# 指定文件格式
file_format = '*.xlsx'
# 使用glob模块获取文件路径
files = glob.glob(folder_path + file_format)
# 打印文件路径
for file in files:
print(file)
ClickHouse关联交互:
ClickHouse是一个快速、高效的列式分布式数据库管理系统(一种OLAP类型的列式数据库管理系统),最初由Yandex开发。它支持高度并行的查询处理,能够处理PB级别的数据,并提供了灵活的数据模型。ClickHouse具有以下关键特点:
- 列式存储:数据按列而非按行存储,有利于大规模数据的压缩和查询性能。
- 并行处理:支持并行查询,可同时处理多个查询任务,提高查询速度。
- 分布式架构:具备横向扩展的能力,可以在多台服务器上分布数据。
- 优化查询引擎:通过使用高效的查询执行引擎提高性能,支持复杂的查询操作。
常见的MySQL、Oracle、SQL Server等数据库都是行式数据库,常见的列式数据库有hbase、clickhouse、Vertica等(存储内容一样,但存储方式不一样)。
OLTP全称是On-line Transaction Processing,是一种联机事务型数据库,典型的数据库就是关系型数据库,OLTP关注的是对业务数据的增删改查,面向用户的事务操作,追求效率的最优解。
OLAP全称是On-Line Analytical Processing,是一种联机分析处理数据库,一般用于数据仓库或者大数据分析处理,这种类型的数据库在事务能力上很弱,但是在分析的场景下很强大,关键性的场景:
1、绝大多数是读请求
2、数据以相当大的批次(> 1000行)更新,而不是单行更新;或者根本没有更新。
3、已添加到数据库的数据不能修改。
4、对于读取,从数据库中提取相当多的行,但只提取列的一小部分。
5、宽表,即每个表包含着大量的列
6、查询相对较少(通常每台服务器每秒查询数百次或更少)
7、对于简单查询,允许延迟大约50毫秒
8、列中的数据相对较小:数字和短字符串(例如,每个URL 60个字节)
9、处理单个查询时需要高吞吐量(每台服务器每秒可达数十亿行)
10、事务不是必须的
11、对数据一致性要求低
12、每个查询有一个大表。除了他以外,其他的都很小。
13、查询结果明显小于源数据。换句话说,数据经过过滤或聚合,因此结果适合于单个服务器的RAM中。
clickhouse建表查询操作:
-- CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster] [ENGINE = engine(...)]
CREATE DATABASE IF NOT EXISTS chtest; --使用默认库引擎创建库
-- CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]( name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [compression_codec] [TTL expr1], name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [compression_codec] [TTL expr2], ...) ENGINE = engine
node1 :) use default;
node1 :) create table test(id Int32,name String) engine=Memory; --内存引擎表
node1 :) insert into test(id,name) values(110,'test'); --字符串不能使用双引号
node1 :) select * from test;
-- 分布式DDL操作 默认情况下,CREATE、DROP、ALTER、RENAME操作仅仅在当前执行该命令的server上生效。
-- 创建一个分布式表 ON CLUSTER语句,这样就可以在整个集群发挥作用
CREATE TABLE IF NOT EXISTS user_cluster ON CLUSTER news_ck_cluster(
id Int32, name String) ENGINE = Distributed(news_ck_cluster, default, user_local,id);
-- 表引擎 Distributed(cluster_name, database_name, table_name[, sharding_key])
1、创建分布式表是读时检查的机制,也就是说对创建分布式表和本地表的顺序并没有强制要求。
2、在上面的语句中使用了ON CLUSTER分布式DDL,这意味着在集群的每个分片节点上,都会创建一张Distributed表,这样便可以从其中任意一端发起对所有分片的读、写请求。
3、分布式表映射到每台服务器一张本地表。
# pip install clickhouse-driver
from clickhouse_driver import Client
client = Client('localhost', user='root', password='152617', database='stock_info',port=9000) # 建立与ClickHouse的连接 localhost是ClickHouse的主机地址
result = client.execute('SELECT * FROM stocks')
# 打印查询结果
for row in result:
print(row)
# 关闭数据库连接
client.disconnect()
数据关联操作在实际应用中非常常见,尤其是在数据分析和数据处理领域。通过将不同数据集关联在一起,可以获取更多有用的信息和洞察。ClickHouse作为一个高性能的数据库系统,可以很好地支持这种关联操作。
在实际应用场景中,还可以使用更复杂的关联条件,如多表关联、多字段关联等。ClickHouse提供了丰富的SQL语法和函数,可以满足各种复杂的关联需求。
浮点数的存储
在计算机中,浮点数是以二进制形式存储的。由于浮点数需要同时表示整数部分和小数部分,因此采用IEEE 754标准来存储浮点数。在这个标准中,一个浮点数通常由三部分组成:符号位、指数位和尾数位。指数位用来表示浮点数的指数部分,尾数位用来表示浮点数的小数部分。
当我们将一个浮点数转换为十六进制表示时,实际上是将浮点数的二进制表示转换为十六进制表示。
import struct # 导入struct模块
def float_to_hex(f):
return hex(struct.unpack('<I', struct.pack('<f', f))[0]) # 使用pack方法将浮点数转换为二进制表示
f = 3.14
print(float_to_hex(f)) # 用hex方法将二进制表示转换为十六进制表示
id()函数(对象的内存地址):
每个对象在内存中都有一个唯一的标识符,这个标识符可以帮助我们追踪对象的生命周期和使用情况
x = 10
print(id(x)) # 140733020460464 这个地址是一个十进制数
x = 10
y = x
print(id(x) == id(y)) # True
当对象没有被任何变量引用时,垃圾回收器会将其回收。这意味着对象的内存地址可能会在之后被重用。因此,id()函数返回的地址并不保证在整个程序执行过程中始终保持不变。
内存分析:通过id()函数,我们可以在内存中追踪对象的分配情况。可以用它来分析程序中是否存在内存泄漏或不必要的重复对象。
引用比较:有时,我们需要判断两个变量是否引用了同一个对象。这时可以使用id()函数来比较它们的内存地址。
数据结构中的应用:在一些自定义数据结构中,可能需要用到对象的内存地址作为键值来快速查找对象。这时可以利用id()函数来获取对象的地址。
生成HTML表格:
在HTML中,表格是由<table>
元素定义的。表格中的数据通常是以行(<tr>
)和列(<td>
或<th>
)的形式来排列的。其中,<td>
用于表示普通的表格数据单元,<th>
用于表示表头单元格。
用字符串拼接的方式来生成HTML表格:
def generate_html_table(data):
css_style = "<style>table {width: 100%; border-collapse: collapse;} th, td {border: 1px solid black; padding: 8px;}</style>"
# 添加样式
html = "<table>"
html += "<tr>"
for header in data[0]:
html += f"<th>{header}</th>"
html += "</tr>"
for row in data[1:]:
html += "<tr>"
for cell in row:
html += f"<td>{cell}</td>"
html += "</tr>"
html += "</table>"
return html
# 示例数据
data = [
["姓名", "年龄"],
["张三", 25],
["李四", 30]
]
html_table = generate_html_table(data)
print(html_table)
JSON对象更新:
创建一个简单的JSON对象(字典表示):
import json
# 创建一个JSON对象
student = {
"name": "Alice",
"age": 20,
"major": "Computer Science"
}
# 将JSON对象转换为字符串
student_json = json.dumps(student)
print(student_json) # 字典形式
往JSON对象中添加新的元素时,可以将JSON对象转换为Python字典,然后向字典中添加新的键值对,最后将其转换回JSON格式:
# 将JSON字符串转换为Python字典
student_dict = json.loads(student_json)
# 添加新元素
student_dict["gender"] = "Female"
# 将字典转换为JSON字符串
updated_student_json = json.dumps(student_dict)
print(updated_student_json)
用update()
方法来更新JSON对象:
# 创建一个新的JSON对象
new_student = {
"name": "Bob",
"age": 22,
"major": "Mathematics"
}
# 将JSON字符串转换为Python字典
student_dict = json.loads(student_json)
# 更新JSON对象
student_dict.update(new_student)
# 将字典转换为JSON字符串
updated_student_json = json.dumps(student_dict)
print(updated_student_json)
用json.loads()
方法将JSON字符串转换为Python字典,然后通过操作字典来添加、更新、删除元素,最后使用json.dumps()
方法将字典转换回JSON格式。
读取日志获取每行数据并自动关闭文件:
log_file_path = 'example.log'
with open(log_file_path, 'r') as file:
for line in file:# 获取每一行
# 解析日志数据
timestamp, level, message = line.split(' ', 2)
# 输出日志数据
print(f'Timestamp: {timestamp}, Level: {level}, Message: {message}')
大写转小写:
# 使用 lower() 方法将大写字母转换为小写字母
s_lower = s.lower()
# 循环大写转小写
# 定义一个包含大写字母的字符串
s = "HELLO WORLD"
s_lower = ""
for char in s:
if ord('A') <= ord(char) <= ord('Z'):
char_lower = chr(ord(char) + 32) # 大写转小写的 ASCII 编码
else:
char_lower = char
s_lower += char_lower
print(s_lower)
判断是不是数字:
1、type()函数
# 判断整数
num1 = 10
print(type(num1) is int) # True
# 判断浮点数
num2 = 3.14
print(type(num2) is float) # True
# 判断复数
num3 = 1j
print(type(num3) is complex) # True
2、isinstance()函数:接受一个类型(如int、float、complex)或一个类型的元组作为参数
# 判断整数
num1 = 10
print(isinstance(num1, int)) # True
# 判断浮点数
num2 = 3.14
print(isinstance(num2, float)) # True
# 判断复数
num3 = 1j
print(isinstance(num3, complex)) # True
3、正则表达式
import re
# 判断整数
num1 = '123'
print(bool(re.match(r'^[0-9]+', num1))) # True
# 判断浮点数
num2 = '3.14'
print(bool(re.match(r'^[0-9]+.[0-9]+', num2))) # True
# 判断复数
num3 = '1j'
print(bool(re.match(r'^[0-9]+[+|-]*[0-9]*j$', num3))) # True
order by 和group by 联系区别:
ORDER BY
子句用于对查询结果集进行排序。它可以出现在SQL查询的SELECT语句中,以便对返回的行进行排序,使其按照一个或多个列的值排列。默认情况下,ORDER BY
按照升序排序,但是可以使用 DESC
关键字来实现降序排序。
GROUP BY
子句用于将查询结果集中的行分组,通常与聚合函数(如 COUNT()
, SUM()
, AVG()
, MAX()
, MIN()
等)一起使用,以便对每个组的数据进行汇总、计算统计值或进行其他操作。
联系
ORDER BY
和 GROUP BY
的联系在于它们都可以用来对查询结果进行某种形式的数据整理。在某些复杂的查询中,它们可以一起使用,首先通过 GROUP BY
对数据进行分组,然后对每个组的结果进行排序。
区别
- 目的不同:
ORDER BY
用于排序,GROUP BY
用于分组。 - 位置不同:在SQL查询中,
GROUP BY
必须出现在ORDER BY
之前。 - 功能不同:
ORDER BY
不会改变数据的本质,只是改变显示的顺序;而GROUP BY
则通常与聚合函数一起使用,对数据进行汇总或计算。 - 适用场景:
ORDER BY
适用于任何需要查看数据顺序的场景;GROUP BY
适用于需要对数据进行分组分析的场景。
where 和having 联系区别:
WHERE
子句用于在查询过程中对数据进行筛选,它作用于查询的原始数据集,即在数据被聚合之前进行筛选。WHERE
可以用于任何没有聚合函数的查询中,用来指定条件,只有满足条件的行才会被包含在结果集中。
HAVING
子句也用于筛选数据,但它是在数据被聚合之后进行筛选的。HAVING
通常与 GROUP BY
子句一起使用(在 GROUP BY之后
),用于对分组后的数据进行条件筛选。HAVING
可以包含聚合函数,因为它是在聚合操作之后应用的。
联系
WHERE
和 HAVING
的联系在于它们都是用于筛选数据的子句,都可以用来指定条件,以控制哪些数据行应该被包含在查询结果中。
区别
- 使用时机:
WHERE
在数据聚合之前使用,而HAVING
在数据聚合之后使用。 - 适用场景:
WHERE
适用于任何查询,而HAVING
通常与GROUP BY
一起使用,用于对分组后的数据进行筛选。 - 聚合函数:
WHERE
不能直接使用聚合函数,因为它在聚合之前执行;而HAVING
可以直接使用聚合函数,因为它在聚合之后执行。 - 执行顺序:在SQL查询中,
WHERE
通常在FROM
和GROUP BY
之后执行,而HAVING
在GROUP BY
之后执行。
reduce
过程:
在MapReduce编程模型中,reduce
过程是整个数据处理流程的最后阶段,它负责将 map
阶段输出的中间结果进行合并和处理,以生成最终的输出结果。reduce
过程通常用于执行聚合操作,如求和、计数、平均值等。
以下是 reduce
过程的详细步骤:
-
Shuffle阶段:
- 在
map
阶段完成后,MapReduce框架会将所有map
任务输出的键值对根据键进行排序,并将相同键的值分组到一起。 - 这个排序和分组的过程称为“shuffle”,它确保了相同键的值能够被同一个
reduce
任务处理.
- 在
-
复制(Copy)阶段:
- 在这个阶段中,MapReduce 框架会将
map
阶段输出的数据复制到reduce
任务的本地存储中,以便在进行后续处理时能够更快地访问数据。 - 这个阶段在默认情况下是并行执行的,因此会启用多线程来加快数据复制速度。
- 含一个eventfetcher获取一个已完成的map表,由fetch线程去copy数据,启动两个merge线程(imemorymerge 和 ondiskmerge)
- 在这个阶段中,MapReduce 框架会将
-
Sort阶段:
- Shuffle之后,数据会被进一步排序,以确保所有属于同一个键的值都是连续的,这样
reduce
任务可以高效地处理它们。
- Shuffle之后,数据会被进一步排序,以确保所有属于同一个键的值都是连续的,这样
-
Reduce阶段:
- 每个
reduce
任务会接收到一组键值对,其中键是唯一的,而值是与该键相关联的所有中间值的列表。 reduce
任务会遍历这个列表,并应用用户定义的reduce
函数来处理这些值。reduce
函数的目的是将这些值合并成一个或多个最终的输出值。- 例如,如果
reduce
函数是求和,那么它会将所有与同一个键关联的值相加。
- 每个
-
输出结果:
reduce
任务处理完所有键值对后,会将最终的输出写入到分布式文件系统(如HDFS)中,或者输出到指定的存储位置。- 每个
reduce
任务的输出通常是一个新的键值对集合,这些键值对构成了整个MapReduce作业的最终结果。
reduce
过程的关键在于它能够处理大规模数据集中的聚合操作,通过将数据分散到多个节点上并行处理,MapReduce模型能够有效地处理海量数据。
mysql优化:
MySQL数据库优化是一个广泛的领域,涉及多个方面,如查询优化、索引设计、存储引擎选择、服务器配置等。以下是一些基本的优化建议:
-
查询优化:
- 使用索引:为经常用于 WHERE 子句的列创建索引,避免全表扫描。
- **避免使用 SELECT *``:只选择需要的列,减少数据传输和处理。
- **合理使用 JOIN``:尽量减少连接的表数量,使用 INNER JOIN 而不是 OUTER JOIN,如果可能,考虑使用连接子查询。
- **避免在 WHERE`` 子句中使用函数,因为函数会破坏索引的使用。
-
索引策略:
- 选择合适的索引类型:B-Tree 索引适合大多数情况,哈希索引适合等值查找,全文索引适合文本搜索。
- 避免过多或过少的索引:过多的索引会增加存储和维护成本,过少的索引可能导致查询性能下降。
-
存储引擎选择:
- **MyISAM``:适合读多写少的场景,但不支持事务。
- **InnoDB``:支持事务,适合频繁写入和需要事务处理的场景。
- **Memory``:存储在内存中的引擎,适合缓存频繁读取的数据。
-
服务器配置:
- 调整缓冲区大小:增大
innodb_buffer_pool_size
可提高读取性能。 - 分区表:根据数据分布和查询模式对大表进行分区,可以提高查询速度。
- 优化查询缓存:开启查询缓存,但要注意内存使用情况。redis
- 调整缓冲区大小:增大
-
定期维护:
- 优化查询计划:使用
EXPLAIN
分析查询计划,找出慢查询并优化。 - 定期清理无用数据:定期删除过期或不再需要的数据。
- 优化查询计划:使用
-
硬件优化:
- 增加内存:内存大小直接影响缓存能力,提高性能。cpu/disk
- **优化磁盘 I/O``:使用 SSD 替换机械硬盘,减少 I/O 操作时间。
锁:
锁是用来控制多个用户对同一数据资源进行并发访问的机制。以下是几种常见的锁类型及其特点:
-
共享锁(Shared Locks,简称S锁):
- 共享锁允许多个事务同时读取同一资源,但不允许任何事务修改该资源。
- 当一个事务对某行数据加上共享锁后,其他事务也可以对该行数据加共享锁,但不能加排他锁。
- 共享锁通常用于读操作,以保证数据的一致性。
-
排他锁(Exclusive Locks,简称X锁):
- 排他锁允许持有锁的事务对资源进行读写操作,但阻止其他事务获取任何类型的锁。
- 当一个事务对某行数据加上排他锁后,其他事务既不能加共享锁也不能加排他锁。
- 排他锁通常用于写操作,以保证数据的完整性。
-
行级锁(Row-Level Locks):
- 行级锁是指锁定数据库表中的单个行记录。
- 行级锁的粒度小,可以减少锁冲突,提高并发性能。
- InnoDB存储引擎支持行级锁。
-
意向锁(Intention Locks):
- 意向锁是一种表级锁,它表明一个事务稍后将对表中的行加何种锁(共享锁或排他锁)。
- 意向锁分为意向共享锁(IS)和意向排他锁(IX)。
- 意向锁的目的是为了提高锁的兼容性,减少锁冲突。
-
表级锁(Table-Level Locks):
- 表级锁是指锁定整个数据库表,而不是单个行。
- 表级锁的粒度大,容易导致锁冲突,降低并发性能。
- MyISAM存储引擎使用表级锁。
-
单行记录加锁:
- 在InnoDB存储引擎中,可以通过特定的SQL语句对单行记录加锁。
- 例如,使用
SELECT ... FOR UPDATE
可以对选中的行加排他锁,使用SELECT ... LOCK IN SHARE MODE
可以加共享锁。 - 这些锁只在事务提交或回滚后释放。
在实际应用中,选择合适的锁策略对于保证数据的一致性和提高系统的并发性能至关重要。通常,行级锁由于其粒度小,能够提供更好的并发控制,但管理成本也相对较高。表级锁虽然管理简单,但在高并发环境下可能会导致性能瓶颈。
算法效率
一个问题会有多种解法,需要对算法的优劣进行判断哪种是最优的,这时候就要对复杂度有一定程度的了解,如:斐波那契数列的两个实现方式,一种递归,一种循环
def fibonacci_loop(n):
fib_sequence = [0, 1]
for i in range(2, n):
fib_sequence.append(fib_sequence[i-1] + fib_sequence[i-2])
return fib_sequence
n = int(input("请输入斐波那契数列的数量:"))
fib_sequence = fibonacci_loop(n)
print(fib_sequence)
def fibonacci_recursive(n):
if n <= 1:
return n
else:
return fibonacci_recursive(n-1) + fibonacci_recursive(n-2)
n = int(input("请输入斐波那契数列的数量:"))
fib_sequence = [fibonacci_recursive(i) for i in range(n)]
print(fib_sequence)
算法效率分析分为两种:第一种是时间效率,第二种是空间效率。时间复杂度主要衡量的是一个算法的运行次数,而空间复杂度主要衡量一个算法所需要的额外空间,在摩尔定律下,现在计算机的存储容量达到很高的程度,反而我们现在最需要关注的就是一个算法的时间效率。
一个算法源文件(.c)经过编译、链接后生成可执行文件(.exe)后,运行时会对计算机造成一定的资源消耗,时间消耗是对CPU的处理速度,空间消耗是对内存栈区(Stack)的空间
总结:衡量一个算法的好坏,就是从时间和空间两个维度来衡量。
时间复杂度:
时间复杂度:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。一个算法所花费的时间与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的时间复杂度。而不是一个算法执行完成的所需要花费的时间。
大O的渐进表示法(大O符号(Big O notation):用于描述函数渐进行为的数学符号)
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
4、通过大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
#eg:
def cal(N:int):
total=0
for i in range(N): # 第一段
for j in range(N):
total += 1
for k in range(2*N): # 第二段
total += 1
for m in range(10): # 第三段
total += 1
return total
cal函数是有三个循环组成,由定义可知执行了多少次就是我们想要的时间复杂度
第一段嵌套for循坏执行次数是就是 N * N,因为有两层循环,第一层和第二层都是走到N才能停止
第二段for循坏执行次数是 2 * N,因为K = 0 要递增到 2 * N才能停止
第三段while循环执行次数是 10,因为 M = 10,M进入循环递减到0,才能停止
所以cal(N) = N² + 2 * N + 10
当n越来越大的时候不是最高阶项数对结果影响会越来越小
使用大O的渐进表示法以后,cal的时间复杂度为:O(N²)
总结:大O的渐进表示法就是看程序在哪个阶级的
拿刚才的cal来举例
N = 10 cal(N) = 10 * 10 + 2 * 10 + 10 = 130 此时最高项数是100,较低项数和是30
N = 100 cal(N) = 100 * 100 + 2 * 100 + 100 = 10300 此时最高项数是10000,较低项数和是300
另外有些算法的时间复杂度存在最好、平均和最坏情况:
- 最坏情况:任意输入规模的最多运行次数
- 平均情况:任意输入规模的期望运行次数
- 最好情况:任意输入规模的最少运行次数
def find_num(arr:list, num:int):
count = 0
for i in arr:
count += 1
if i == num:
return arr.index(i), count
arr = [1,2,3,4,5,6,7,8,9]
print(find_num(arr, 1)) # best (0, 1)
print(find_num(arr, 5)) # expect (4, 5)
print(find_num(arr, 9)) # bad (8, 9)
实际中一般关注的是算法的最坏运行情况,所以数组中搜索数字时间复杂度为O(N)
eg1:O(N)
def cal(N:int):
total=0
for k in range(2*N):
total += 1
for m in range(10):
total += 1
return total
k = 0,k要递增到 k = 2 * N,才能结束
m = 10;要执行10次,所以精确执行次数是2N + 10
用大O渐进表示法最后时间复杂度是O(N)
def cal(arr:list, st:[int,str]):
total=0
for k in arr:
total += 1
if k == st:
return arr.index(k), total
elif total == len(arr):
return total
cal([1,2,3,4,5,6,7,'we'],0)
看最坏情况,要么是找字符串最后一个或者就是没有找到时间复杂度是O(N)
def cal(N:int, M:int):
total=0
for k in range(N):
total += 1
for k in range(M):
total += 1
return total
第一个for循环k = 0,k < M,需要执行M次才能结束
第二个for循环k = 0,k < N,需要执行N次才能结束
只是在M和N不确定谁大情况下,时间复杂度是O(M + N)
M和N相等,时间复杂度是O(M) 或者 O(N)
M远大于N,时间复杂度是O(M)
N远大于M,时间复杂度是O(N)
def Factorial(N):
if N == 2:
return 2
else:
return Factorial(N-1) * N
阶乘递归函数复杂度:递归次数 * 每次递归调用的次数
递归了N次,每次调用了2次,其中比较了1次,还有最后return的时候计算了1次,所以是常数次,所以是N * 常数次,即这里每次调用递归次数为1次,还是O(N)
def feb(N):
a=[0,1]
for i in range(2,N):
a.append(a[i-1]+a[i-2])
return a,a[N-1]
# 循环N次结束循环,前i项是已知的,所以为O(N)
eg2:O(1)
def cal(N:int):
total=0
for k in range(100):
total += 1
return total
k = 0,要递增到100的时候循环才能停止,看到一个明确的次数,都要把它看成O(1),理解成是1的线性函数
eg3:O(N²)
# 冒泡排序:重复地遍历要排序的列表,比较相邻元素,并交换它们的位置,直到整个列表按照升序排列(逻辑是从大到小排)。
def bubble_sort(lst):
n = len(lst)
for i in range(n-1):
for j in range(n-i-1):
if lst[j] > lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j]
# print("排序后的列表:", lst)
lst = [64, 34, 25, 12, 90, 22, 11]
bubble_sort(lst)
# 错误想法:不要看有x个循环嵌套,就是O(N^x)
# 要对N个进行排序,每次还需要交换N-1,N-2,N-3,…… 2 ,1,把次数相加,类似等差数列,公差是1,首项是N-1,尾项是1,项数是N-1 = (N-1)*(1+N-1)/2 = O(N^2)
eg4:O(log2(N))
# 二分查找:在已排序的数组或列表中查找特定元素。通过将待查找的范围不断缩小一半,从而快速定位目标元素
def binary_sort(arr: list, num) -> str:
total = 0
start = 0
end = len(arr) - 1
while start <= end:
total += 1
mid = (start + end) // 2
if arr[mid] == num:
return total
elif arr[mid] < num:
start = mid + 1
else:
end = mid - 1
return 'not found'
print(binary_sort([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4))
# 二分查找的最好情况是O(1),折半第一次的时候的正好就找到了
# 最坏情况是找最左边或者最右边,还有就是找不到: N/2/2/……/2 = 1, 折半了几次,就是查找了几次,设折半了x次,2^x= N,x = log2(N)
"""
def binary_sort(arr:list, num):
total = 0
start = 0
end = len(arr)-1
while start != end:
total += 1
i = int(len(arr) / 2)
if arr[i] == num:
break
elif arr[i] < num:
start = i
arr = arr[start:]
elif arr[i] > num:
end = i
arr = arr[:end]
start = 0
end = len(arr)-1
if start == end:
return 'not found'
print(arr,start,end)
return total
binary_sort([1,2,3,4,5,6,7,8,9],1)
"""
eg5:O(2^N)
def feb(N):
if N <= 2:
return 1
else:
return feb(N-1) + feb(N-2)
# 斐波那契数列时间复杂度:递归次数 * 每次递归调用的次数
# 共递归N次,每次递归调用递归函数2次,第二次递归就需要调用2^2次,以此类推为O(2^N)
# 递归总次数为2^0+2^1+...+2^(N-1)-X (其中X为递归调用缺的一部分)
# 等比项为2 = 2*(1-2^(N-1))/(1-2) = O(2^N)
eg6:O(sqrt(N))
def cal(N):
X = 0
total = 0
while N > (X+1) * (X+1):
total += 1
X += 1
return total
空间复杂度
对一个算法在运行过程中临时额外占用存储空间大小的量度 。空间复杂度不是程序占用 了多少bytes的空间,因为这个也没太大意义,所以空间复杂度算的是变量的个数。空间复杂度计算规则基本跟时间复杂度类似,也使用大O渐进表示法。
注意:函数运行时所需要的内存栈空间(储存参数、局部变量、一些寄存器信息等)在编译期间已经确定好了,因此空间复杂度主要通过函数运行时显式申请的额外空间来确定。
总结:一个程序运行需要额外显式定义变量个数
eg1:O(1)
def bubble_sort(lst):
n = len(lst) # 常数空间,仅用于存储列表长度
for i in range(n-1): # 控制逻辑,不占用额外的空间
for j in range(n-i-1): # 循环变量 i 和 j 是常数大小的变量
if j == 1:
print(id(j)) # 打印j的内存地址,临时的调试输出(Python使用垃圾回收机制和内存管理,可能会对对象的内存地址进行优化,不是直接显示内存的物理地址,而是一个标识符,用来唯一标识该对象)
if lst[j] > lst[j+1]:
lst[j], lst[j+1] = lst[j+1], lst[j] # 原地交换元素,不需要额外的存储空间
lst = [64, 34, 25, 12, 90, 22, 11]
bubble_sort(lst)
# 有三个变量临时分别是n,i,j,一共3个额外的空间, j在第二层循环里,这个循环要执行n次,但是循环n次里面的j都是一个空间,在内存栈区,创建再销毁,创建再销毁
# 每次输出刚进循环的j,地址都是一样的,空间复杂度是O(1)
eg2:O(N)
def feb(N):
a=[0,1] # 初始化一个包含两个元素的列表,所以空间复杂度为 O(2),即常数空间
for i in range(2,N):
a.append(a[i-1]+a[i-2])# 列表 a 会被逐渐扩展,直到包含 N 个元素(a[N-1]),因此 a 列表的最终空间需求是 O(N)
return a,a[N-1]
def feb(N): # 每次递归调用都需要在栈上保存当前的函数状态,包括参数 N 和临时变量,所以递归的深度是 O(N)
if N <= 2:
return 1
else:
return feb(N-1) + feb(N-2) # 在调用栈上生成一个新的函数调用,直到递归到基例 N <= 2
常见复杂度:
que:
数组nums
包含从0
到n
的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。时间复杂度限制:O(n)
输入:[9,6,4,2,3,5,7,0,1] 输出:8
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]