一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-优快云博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
目录
我的写法
import math
N=int(input())
nums=input().split()
legal_num=N
output=0.0
for i in range(N):
if nums[i].isalpha() or nums[i].count('.')>1:
print(f"ERROR: {nums[i]} is not a legal number")
legal_num-=1
elif float(nums[i])<-1000 or float(nums[i])>1000:
print(f"ERROR: {nums[i]} is not a legal number")
legal_num-=1
elif nums[i].count('.')==1 and len(nums[i].partition('.')[2])>2:
print(f"ERROR: {nums[i]} is not a legal number")
legal_num-=1
else:
output+=float(nums[i])
if legal_num==0:
print("The average of 0 numbers is Undefined")
elif legal_num==1:
print(f"The average of 1 number is {output:.2f}")
else:
output/=legal_num
print(f"The average of {legal_num} numbers is {output:.2f}")
时间复杂度
代码的时间复杂度主要由以下部分决定:
- 读取输入:O(N)
- 遍历每一个输入并验证合法性:O(N)
- 打印输出信息:O(1)
因此,整体时间复杂度为:O(N)。
空间复杂度
代码的空间复杂度主要由以下部分决定:
- 存储输入数值字符串的列表 nums:O(N)
- 存储一些常量(如 legal_num 和 output):O(1)
因此,整体空间复杂度为:O(N)。
总结
这段代码实现了对输入数值的合法性检测和平均值计算,逻辑清晰,操作简单。时间复杂度和空间复杂度均为 O(N),适用于输入数据量较大的情况。需要注意的是,这段代码假设输入的初始 N 和数值字符串是有效的,未处理可能的输入异常(如非整数的 N)。
我要更强
考虑到这段代码的主要功能是读取并处理输入字符串来计算平均值,其时间复杂度和空间复杂度已经较为高效,但仍有一些优化和改进的空间。
优化点
- 合并条件检查:减少重复的条件检查。
- 减少冗余计算:避免重复调用某些函数。
- 异常处理优化:对于转换为浮点数的操作,可以通过异常捕获来简化条件判断。
优化后的代码
def is_legal_number(num):
try:
# 尝试将 num 转换为浮点数
val = float(num)
# 检查数值范围
if val < -1000 or val > 1000:
return False
# 检查小数点后的位数
if '.' in num and len(num.split('.')[1]) > 2:
return False
return True
except ValueError:
# 转换失败说明不是合法的数字
return False
def main():
import sys
input = sys.stdin.read
data = input().split()
N = int(data[0])
nums = data[1:N+1]
legal_num = 0
output = 0.0
for num in nums:
if is_legal_number(num):
output += float(num)
legal_num += 1
else:
print(f"ERROR: {num} is not a legal number")
if legal_num == 0:
print("The average of 0 numbers is Undefined")
elif legal_num == 1:
print(f"The average of 1 number is {output:.2f}")
else:
output /= legal_num
print(f"The average of {legal_num} numbers is {output:.2f}")
if __name__ == "__main__":
main()
优化分析
整体时间复杂度仍然是 O(N),因为我们仍然需要遍历输入的每一个数值并进行合法性检查。
- 时间复杂度优化:
- 使用 try-except 块简化了条件检查,减少了冗余的检查次数。
- 通过 split 函数直接拆分输入,避免了多次调用相同的字符串操作函数。
- 空间复杂度优化:
- 使用 sys.stdin.read 一次性读取输入数据并拆分,避免了多次读取操作,这减少了内存的额外使用。
- 函数 is_legal_number 封装了合法性检查逻辑,使得代码更加模块化和易读。
整体空间复杂度仍然是 O(N),因为需要存储输入的数值字符串列表。
总结
通过以上优化,使代码更加模块化和简洁,减少了冗余的条件检查,并保持了整体的时间和空间复杂度。这些改进使代码更具有可读性和可维护性。
哲学和编程思想
在优化代码的过程中,运用了多种编程哲学和思想,这不仅使代码更加高效,还提高了其可读性和可维护性。以下是详细说明:
1. 模块化设计
哲学和思想:
- 单一职责原则 (Single Responsibility Principle):每个函数只有一个明确的职责。
- 模块化 (Modularity):将不同功能封装在独立的模块中。
具体应用:
- 将合法性检查封装在
is_legal_number
函数中,使得主函数main
更加简洁和清晰。
2. 异常处理
哲学和思想:
- 防御性编程 (Defensive Programming):通过异常处理来捕捉并处理潜在的错误。
- 优雅退化 (Fail Gracefully):在遇到错误时,程序能够优雅地处理而不是崩溃。
具体应用:
- 使用
try-except
块来捕捉非数值输入带来的异常,避免了多次条件检查。
3. 减少冗余
哲学和思想:
- 不重复自己 (Don't Repeat Yourself, DRY):减少代码中的重复部分。
- 简洁性 (Simplicity):保持代码简洁,避免不必要的复杂性。
具体应用:
- 合并条件检查,避免重复进行多次浮点转换和字符串操作。
4. 高内聚低耦合
哲学和思想:
- 高内聚 (High Cohesion):相关的功能放在一起,使得代码模块能够自洽地完成其职责。
- 低耦合 (Low Coupling):模块之间的依赖性低,修改一个模块不会影响到其他模块。
具体应用:
- 合法性检查与输入输出分离,使得这两个部分互不依赖,可以独立测试和修改。
5. 面向接口编程
哲学和思想:
- 面向接口编程 (Programming to an Interface):通过接口来定义功能,而不是具体实现。
- 抽象 (Abstraction):隐藏实现细节,只暴露必要的功能。
具体应用:
is_legal_number
函数定义了一个接口,用于检查数值是否合法,具体的实现细节被隐藏在函数内部。
6. 输入数据的预处理
哲学和思想:
- 前期筛选 (Preprocessing):在正式处理前对数据进行预处理,避免在后续操作中反复处理同样的数据。
具体应用:
- 使用
sys.stdin.read
一次性读取所有输入数据,拆分后再处理,减少了多次读取输入的开销。
总结
通过这些编程哲学和思想,不仅优化了代码的性能,还提高了其可读性和可维护性。模块化设计、异常处理、减少冗余、高内聚低耦合、面向接口编程和输入数据的预处理等思想在代码中得到了具体体现。这些理念不仅帮助我们编写出高质量的代码,也为后续的维护和扩展奠定了坚实的基础。
举一反三
运用编程哲学和思想可以帮助你编写高质量、易维护的代码。以下是一些具体技巧和实践,帮助你在日常编程中举一反三,灵活运用这些思想:
1. 模块化设计
技巧:
- 功能分解:将每个功能块拆分成独立的函数或类,确保每个函数/类只负责一个任务。
- 定义清晰接口:为每个模块定义清晰的接口,只暴露必要的功能。
- 避免大函数:如果一个函数超过20-30行代码,考虑拆分成多个更小的函数。
2. 异常处理
技巧:
- 防御性编程:在可能发生错误的地方进行异常处理,防止错误蔓延。
- 优雅退化:在捕获异常后尽可能提供有用的错误信息,并保持程序继续运行。
3. 减少冗余
技巧:
- 不重复自己 (DRY):提取重复的代码片段到函数中,避免在多个地方出现相同代码。
- 使用数据结构和库函数:尽量使用高效的数据结构和库函数,避免自己实现复杂的逻辑。
4. 高内聚低耦合
技巧:
- 高内聚:确保一个模块或类内的功能紧密相关,单一职责。
- 低耦合:模块之间的依赖性尽量低,不要轻易引用其他模块的实现细节。
5. 面向接口编程
技巧:
- 编程面向接口:通过接口(函数、类方法)来定义模块的功能,而不是直接实现细节。
- 抽象层次:通过抽象层次来隐藏实现细节,只暴露必要的接口。
6. 输入数据的预处理
技巧:
- 提前预处理:在正式处理数据之前,先进行必要的预处理,以减少后续步骤中的重复工作。
- 数据清洗:确保数据在进入逻辑处理之前已经被清洗和验证。
7. 使用高效的数据结构
技巧:
- 选择合适的数据结构:根据具体问题选择高效的数据结构,如列表、集合、字典等。
- 避免不必要的拷贝:尽量避免在循环中频繁创建或拷贝数据。
总结
通过运用这些技巧和实践,可以在编写代码时灵活应用编程哲学和思想,提高代码的质量和可维护性。这些技巧不仅有助于解决当前问题,还能为在面对更复杂的编程挑战时提供有力的指导。