一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
目录
我的写法
N=int(input())
month_eng_to_chi={'Jan':'01',
'Feb':'02',
'Mar':'03',
'Apr':'04',
'May':'05',
'Jun':'06',
'Jul':'07',
'Aug':'08',
'Sep':'09',
'Oct':'10',
'Nov':'11',
'Dec':'12',
}
for i in range(N):
date=input().split()
month=month_eng_to_chi[date[0]]
day=date[1][:-1].zfill(2)
year=date[2].zfill(4)
output=year+month+day
if output==output[::-1]:
print(f"Y {output}")
else:
print(f"N {output}")
这段代码的主要功能是读取 N 个日期,将日期转换为特定格式(yyyymmdd),并判断该格式的日期是否是回文数。如果是回文数,则输出 Y 和日期格式,否则输出 N 和日期格式。
代码点评
- 功能实现:
- 将月份英文缩写转换为两位数字。
- 将日期中的日和年转换为两位和四位数字,确保格式统一。
- 检查转换后的日期是否是回文数,并输出结果。
- 代码结构:
- 代码结构简单明了,易于理解。
- 使用字典 month_eng_to_chi 进行月份转换,提高了代码的可读性和维护性。
时间复杂度分析
- 初始化字典 month_eng_to_chi:
- 字典初始化的时间复杂度为 O(1),因为这是一个常量操作。
- 主循环:
- 循环次数为 N,每次循环内部操作如下:
- date=input().split():假设输入字符串长度为 L(一般情况下可以认为 L 是常数),时间复杂度为 O(L) ≈ O(1)。
- month=month_eng_to_chi[date[0]]:字典查找操作的时间复杂度为 O(1)。
- day=date[1][:-1].zfill(2) 和 year=date[2].zfill(4):字符串处理操作的时间复杂度为 O(1)。
- output=year+month+day:字符串连接操作的时间复杂度为 O(1)。
- output==output[::-1]:字符串反转和比较操作的时间复杂度为 O(7) ≈ O(1)(因为日期格式固定为7位)。
- 打印操作 print(f"Y {output}") 或 print(f"N {output}"):时间复杂度为 O(1)。
综上,每次循环的时间复杂度为 O(1),因此整个代码的总时间复杂度为 O(N)。
空间复杂度分析
- 字典 month_eng_to_chi:
- 字典的空间复杂度为 O(12),因为它包含12个键值对。
- 输入存储:
- 输入的日期字符串和各个分割后的部分占用的空间为 O(L) ≈ O(1)。
- 字符串处理:
- month、day、year 和 output 都是固定长度字符串,其空间复杂度为 O(1)。
- 整体空间复杂度:
- 主循环中每个变量(如 date、month、day、year 和 output)在每次迭代结束后可以被垃圾回收,因此总的空间复杂度为 O(1)(常数空间)。
综上所述:
- 时间复杂度:O(N)
- 空间复杂度:O(1)
优化建议
这段代码在效率和空间上已经非常优化,但从代码规范和鲁棒性的角度,以下是一些建议:
- 输入合法性检查:
- 增加对输入日期格式的检查,确保输入合法。例如可以检查月份是否在字典中、日期和年份是否能正确转换。
- 代码注释:
- 增加适当的代码注释,帮助理解代码逻辑。
- 函数模块化:
- 将主要逻辑封装在函数中,提升代码的可读性和可复用性。
我要更强
在这段代码中,由于主要工作是对输入进行字符串处理和判断回文,所以时间复杂度和空间复杂度已经非常优化。以下是一些微小的改进,虽然对时间复杂度和空间复杂度的影响有限,但可以提高代码可读性和维护性。
优化建议
- 减少重复计算:将日期格式化和回文判断分开处理。
- 使用生成器:减少中间变量的使用,尽量利用生成器。
- 函数模块化:将主要逻辑封装在函数中,提升代码的可读性和可复用性。
- 预处理输入:在读取输入时就进行基本的合法性检查。
完整代码和注释
def month_to_number(month):
"""将月份英文缩写转换为两位数字字符串。"""
month_eng_to_chi = {
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05',
'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10',
'Nov': '11', 'Dec': '12'
}
return month_eng_to_chi.get(month)
def format_date(date_str):
"""将输入的日期字符串转换为 yyyymmdd 格式。"""
date_parts = date_str.split()
if len(date_parts) != 3:
return None # 输入格式不正确
month = month_to_number(date_parts[0])
if not month:
return None # 月份不合法
day = date_parts[1][:-1].zfill(2)
year = date_parts[2].zfill(4)
return year + month + day
def is_palindrome(s):
"""检查字符串是否为回文。"""
return s == s[::-1]
def main():
"""主函数,处理输入和输出。"""
import sys
input = sys.stdin.read
data = input().strip().split('\n')
N = int(data[0])
dates = data[1:N+1]
for date_str in dates:
formatted_date = format_date(date_str)
if not formatted_date:
print("Invalid date format")
elif is_palindrome(formatted_date):
print(f"Y {formatted_date}")
else:
print(f"N {formatted_date}")
if __name__ == "__main__":
main()
优化分析
- 时间复杂度:
- month_to_number 函数的字典查找时间复杂度为 O(1)。
- format_date 函数主要是字符串处理,时间复杂度为 O(1)。
- is_palindrome 函数的字符串反转和比较操作时间复杂度为 O(7) ≈ O(1)。
- 主循环中的每个操作都是 O(1),因此整体时间复杂度为 O(N)。
- 空间复杂度:
- 字典 month_eng_to_chi 的空间复杂度为 O(12) ≈ O(1)。
- 输入数据的存储空间复杂度为 O(N * L),其中 L 是单行输入的最大长度(一般情况可以视为常数)。
- 由于每次处理的中间变量均为固定长度字符串,且函数局部变量在每次迭代后会被回收,因此整体空间复杂度为 O(1)。
进一步优化
从算法上来说,这段代码已经达到了时间复杂度和空间复杂度的最优。如果考虑进一步优化,可以从以下几个方面入手:
- 输入输出优化:
- 使用更高效的输入输出方法,如 sys.stdin.read 和 sys.stdout.write,减少 I/O 时间。
- 提前终止:
- 在判断回文时,如果发现非回文字符,可以提前终止判断,提高效率。
以下是进一步优化的代码:
def month_to_number(month):
"""将月份英文缩写转换为两位数字字符串。"""
month_eng_to_chi = {
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05',
'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10',
'Nov': '11', 'Dec': '12'
}
return month_eng_to_chi.get(month)
def format_date(date_str):
"""将输入的日期字符串转换为 yyyymmdd 格式。"""
date_parts = date_str.split()
if len(date_parts) != 3:
return None # 输入格式不正确
month = month_to_number(date_parts[0])
if not month:
return None # 月份不合法
day = date_parts[1][:-1].zfill(2)
year = date_parts[2].zfill(4)
return year + month + day
def is_palindrome(s):
"""检查字符串是否为回文,提前终止不必要的检查。"""
length = len(s)
for i in range(length // 2):
if s[i] != s[length - 1 - i]:
return False
return True
def main():
"""主函数,处理输入和输出。"""
import sys
input = sys.stdin.read
data = input().strip().split('\n')
N = int(data[0])
dates = data[1:N+1]
for date_str in dates:
formatted_date = format_date(date_str)
if not formatted_date:
print("Invalid date format")
elif is_palindrome(formatted_date):
print(f"Y {formatted_date}")
else:
print(f"N {formatted_date}")
if __name__ == "__main__":
main()
这种优化主要通过提前终止回文判断,提高了效率,尤其在遇到较长字符串时效果更明显。
哲学和编程思想
在优化和改进这段代码的过程中,应用了多种编程思想和哲学理念。以下是具体的分析:
1. 分治思想
解释: 分治思想是一种解决复杂问题的策略,通过将问题分解为更小的子问题,分别解决这些子问题,最后合并结果来解决原问题。
应用:
- 将日期格式转换和回文判断分开处理。分别定义 month_to_number、format_date 和 is_palindrome 函数,用于处理不同的子任务。这样不仅使得代码更加清晰,而且便于测试和维护。
2. 模块化和函数化编程
解释: 模块化和函数化编程通过将代码分解成独立的函数或模块,使代码更加清晰、易读和可维护。
应用:
- 将主要逻辑封装在函数中,如 month_to_number、format_date 和 is_palindrome,主函数 main 调用这些子函数完成具体任务。这样使得代码结构更加明晰,提高了代码的可复用性和可测试性。
3. 懒惰求值
解释: 懒惰求值是一种只在需要时才计算表达式值的策略,可以提高效率,节省内存。
应用:
- 在 is_palindrome 函数中,提前终止循环。如果在比较过程中发现非回文字符,立即返回 False,避免了不必要的比较。这种策略可以显著提高效率,特别是在处理长字符串时。
4. 单一职责原则
解释: 单一职责原则是指每个模块或函数应承担单一的职责或功能,避免职责混乱,提高系统的可维护性。
应用:
- 各个函数各司其职:month_to_number 只负责月份转换,format_date 只负责格式化日期,is_palindrome 只负责回文判断。通过划分明确的职责,使代码更加清晰和易于维护。
5. 时间和空间复杂度优化
解释: 时间复杂度和空间复杂度是衡量算法效率的重要指标。优化这些复杂度可以提高算法的运行效率和减少资源消耗。
应用:
- 通过减少重复计算、利用哈希表(字典)的快速查找特性、提前终止不必要的计算等手段优化时间复杂度。
- 通过使用局部变量和生成器减少内存占用,优化空间复杂度。
6. 输入输出优化
解释: 在处理大量数据时,高效的输入输出方法可以显著提高程序的性能。
应用:
- 使用 sys.stdin.read 和 sys.stdout.write 代替常规的 input() 和 print(),减少I/O操作时间,提高效率。
7. 鲁棒性和容错设计
解释: 鲁棒性和容错设计是指在代码中加入错误处理和异常处理机制,使程序在面对意外输入或错误时仍能稳定运行。
应用:
在 format_date 函数中加入对输入合法性的检查,确保输入格式正确。对于不合法的输入,返回 None 并进行相应处理。
举一反三
理解编程中的哲学和思想并能够灵活应用是写出高质量代码的重要能力。以下是一些技巧和实践建议,帮助你举一反三,将这些思想应用到不同的编程场景中。
1. 分治思想
技巧:
- 分而治之:将复杂问题分解为更小、更简单的子问题,分别解决这些子问题,再合并结果。
- 递归和迭代:识别问题中的递归结构,利用递归或迭代方法解决问题。
实践:
- 学习和练习经典的分治算法,如快速排序、归并排序等。
- 在解决复杂问题时,首先尝试将其分解为更小的子问题。
2. 模块化和函数化编程
技巧:
- 单一职责:每个模块或函数应只负责一件事。
- 函数封装:将一段逻辑封装到一个函数中,提高代码复用性和可维护性。
- 模块化设计:将代码分成多个模块或包,每个模块有清晰的职责划分。
实践:
- 定期重构代码,将重复的逻辑提取到独立的函数或模块中。
- 设计时考虑模块间的接口,确保模块之间的低耦合。
3. 懒惰求值
技巧:
- 避免不必要的计算:仅在需要时才进行计算,避免提前计算所有可能的结果。
- 短路计算:在布尔运算中利用短路特性,提前终止不必要的计算。
实践:
- 在处理大型数据集或高计算量任务时,考虑采用懒惰求值策略。
- 利用Python的生成器表达式和迭代器实现懒惰求值。
4. 单一职责原则(SRP)
技巧:
- 职责分离:确保每个类、模块或函数只有一个明确的职责。
- 高内聚低耦合:高内聚意味着一个模块内部的功能紧密相关,低耦合意味着模块之间的依赖关系尽可能弱。
实践:
- 在设计类和函数时,问自己“这个类/函数是否只做一件事?”如果答案不确定,考虑进行重构。
- 定期审查代码,确保遵循单一职责原则。
5. 时间和空间复杂度优化
技巧:
- 时间复杂度分析:在设计和实现算法时,分析其时间复杂度,选择最优算法。
- 空间复杂度分析:注意算法的空间复杂度,避免不必要的内存占用。
实践:
- 学习和掌握常见的数据结构和算法,了解它们的时间和空间复杂度。
- 在编写代码时,经常考虑最坏情况的时间和空间复杂度。
6. 输入输出优化
技巧:
- 批量处理:尽量减少I/O操作的次数,采用批量处理的方式。
- 高效I/O操作:使用更高效的I/O方法,如Python中的
sys.stdin.read
和sys.stdout.write
。
实践:
- 在处理大数据时,首先考虑如何优化数据的输入输出。
- 使用合适的文件格式和I/O库,提高读写效率。
7. 鲁棒性和容错设计
技巧:
- 错误处理:在代码中加入适当的错误处理机制,确保程序在发生错误时能够稳定运行。
- 输入验证:对用户输入进行验证,确保输入数据的合法性。
实践:
- 在编写函数时,考虑可能的错误情况,加入适当的异常处理。
- 在处理用户输入时,进行严格的输入验证,防止非法输入导致程序崩溃。
举一反三的技巧
-
多思考、多总结:
- 在解决一个问题后,反思一下自己采用的思路和方法,总结经验教训。
- 尝试将一个问题的解决思路应用到其他类似问题上。
-
主动学习和实践:
- 学习经典算法和设计模式,理解其背后的思想。
- 在实践中刻意应用这些思想,逐步形成自己的编程风格。
-
代码审查和重构:
- 经常进行代码审查,发现和纠正问题。
- 不断重构代码,优化结构和性能。
-
参与开源项目:
- 参与开源项目,学习他人的编码风格和思路,提升自己的编程水平。
具体示例
以下是一个具体的示例,展示如何将以上思想应用到另一个问题中:判断字符串是否是回文。
-
问题描述:给定一个字符串,判断其是否是回文。
-
分治思想:
- 将字符串分成左右两部分,分别判断其各自的回文性质。
-
模块化和函数化编程:
- 编写一个函数
is_palindrome
,只负责判断字符串是否是回文。
- 编写一个函数
-
懒惰求值:
- 在判断回文时,如果发现不匹配字符,立即返回
False
。
- 在判断回文时,如果发现不匹配字符,立即返回
-
单一职责原则:
is_palindrome
函数只负责判断回文,其它逻辑由调用者处理。
-
时间和空间复杂度优化:
- 判断回文时,只需遍历字符串的一半,时间复杂度为 O(n),空间复杂度为 O(1)。
-
鲁棒性和容错设计:
- 对输入字符串进行验证,确保其合法性。