一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
目录
1. 空间-时间权衡(Space-Time Tradeoff)
3. 最小化副作用(Minimize Side Effects)
5. 局部性原理(Principle of Locality)
6. KISS 原则(Keep It Simple, Stupid)
7. DRY 原则(Don't Repeat Yourself)
1. 空间-时间权衡(Space-Time Tradeoff)
3. 最小化副作用(Minimize Side Effects)
5. 局部性原理(Principle of Locality)
6. KISS 原则(Keep It Simple, Stupid)
7. DRY 原则(Don't Repeat Yourself)
我的写法
K = int(input()) # 读取输入的K值,表示需要进行洗牌的次数
orders = list(map(int, input().split())) # 读取输入的顺序列表,表示洗牌后的顺序
# 初始化一副扑克牌
init_cards = []
init_cards += ['S' + str(i + 1) for i in range(13)] # 添加黑桃(S)1到13
init_cards += ['H' + str(i + 1) for i in range(13)] # 添加红桃(H)1到13
init_cards += ['C' + str(i + 1) for i in range(13)] # 添加梅花(C)1到13
init_cards += ['D' + str(i + 1) for i in range(13)] # 添加方块(D)1到13
init_cards.append('J1') # 添加小王(J1)
init_cards.append('J2') # 添加大王(J2)
outputs = [] # 初始化输出列表
for i in range(K): # 进行K次洗牌
outputs = [''] * 54 # 每次洗牌后创建一个空的输出列表
for j in range(54): # 遍历每一张牌
outputs[orders[j] - 1] = init_cards[j] # 根据orders中的顺序将init_cards中的牌放到对应位置
init_cards = outputs # 更新init_cards为新排列的牌,准备进行下一次洗牌
print(*outputs) # 打印最终结果,将输出列表中的牌以空格分隔
这段代码是一个模拟扑克牌洗牌过程的程序,详细分析如下:
功能分析
这段代码的功能是根据用户输入的顺序 orders 和洗牌次数 K 模拟扑克牌的洗牌过程。输入顺序 orders 是一个包含 54 个整数的列表,表示每张牌在洗牌之后的新位置。程序按照这个顺序对扑克牌进行 K 次洗牌,并最终输出洗牌后的扑克牌顺序。
时间复杂度
- 初始化扑克牌:
- 使用列表推导式初始化扑克牌,时间复杂度为 (O(1)),因为这是固定的 54 张牌。
- 洗牌过程:
- 外层循环执行 (K) 次,其中 (K) 是输入的洗牌次数。
- 内层循环执行 54 次,处理每一张牌的移动。
- 整体的时间复杂度为 (O(K \times 54)),即 (O(54K))。通常情况下 (O(54K)) 可以简化为 (O(K)),因为 54 是一个常数。
空间复杂度
- 固定空间:
- init_cards 和 outputs 各有 54 个元素,空间复杂度为 (O(54)),即 (O(1)),因为 54 是常数。
- 其它变量如 K 和 orders(长度为 54)也是常量级别的空间开销。
- 洗牌过程:
- 在每次洗牌中,新的 outputs 列表会覆盖旧的 init_cards 列表,但总的占用空间仍然是两个 54 元素的列表,空间复杂度为 (O(54 \times 2)),即 (O(108))。但这仍然是常数级别,简化为 (O(1))。
总结
- 时间复杂度: (O(K))
- 空间复杂度: (O(1))
代码点评
- 代码结构清晰:初始化扑克牌,循环 K 次洗牌,最后输出结果,逻辑非常清晰。
- 注释明确:每步操作都有详细注释,便于理解。
- 效率合理:时间复杂度和空间复杂度都很低,适用于大多数实际应用场景。
- 可扩展性:如果扑克牌数目或洗牌规则发生变化,代码也易于修改和扩展。
总体来说,这段代码简洁高效,符合专业编程规范。
我要更强
优化这段代码的时间复杂度和空间复杂度并不容易,因为洗牌过程本身需要对每张牌进行重新排列。然而,可以通过减少不必要的内存分配和复制来优化空间使用。以下是几个可能的优化方向:
优化方向
- 内存优化:避免在每次洗牌时创建新的列表,而是使用额外的空间来保存当前和下一步的状态。
- 减少复制操作:尽量减少对列表的整体复制操作。
优化后的代码
通过优化空间使用,我们可以改进代码的内存效率。以下是优化后的代码:
K = int(input()) # 读取输入的K值,表示需要进行洗牌的次数
orders = list(map(int, input().split())) # 读取输入的顺序列表,表示洗牌后的顺序
# 初始化一副扑克牌
init_cards = []
init_cards += ['S' + str(i + 1) for i in range(13)] # 添加黑桃(S)1到13
init_cards += ['H' + str(i + 1) for i in range(13)] # 添加红桃(H)1到13
init_cards += ['C' + str(i + 1) for i in range(13)] # 添加梅花(C)1到13
init_cards += ['D' + str(i + 1) for i in range(13)] # 添加方块(D)1到13
init_cards.append('J1') # 添加小王(J1)
init_cards.append('J2') # 添加大王(J2)
current = init_cards[:] # 当前牌组初始化为init_cards的复制
next_cards = [''] * 54 # 初始化一个空的牌组用于保存下一步结果
for i in range(K): # 进行K次洗牌
for j in range(54): # 遍历每一张牌
next_cards[orders[j] - 1] = current[j] # 根据orders中的顺序将current中的牌放到对应位置
current, next_cards = next_cards, current # 交换current和next_cards,准备进行下一次洗牌
print(*current) # 打印最终结果,将current牌组中的牌以空格分隔
优化分析
- 时间复杂度:
- 外层循环执行 (K) 次,其中 (K) 是输入的洗牌次数。
- 内层循环执行 54 次,处理每一张牌的移动。
- 时间复杂度仍为 (O(K \times 54)),即 (O(K)),因为 54 是常数。
- 空间复杂度:
- 使用了两个固定大小的列表 current 和 next_cards,每个列表包含 54 个元素。
- 总的空间复杂度还是 (O(54 \times 2)),即 (O(1)),因为 54 是常数。
代码点评
- 内存优化:新代码避免了重复分配和复制列表,通过交换引用来保存洗牌结果。
- 逻辑清晰:洗牌过程的逻辑保持不变,代码结构仍然清晰易懂。
- 效率提升:减少了内存分配和复制操作,提高了内存使用效率。
总的来说,这段优化后的代码在保留了原有逻辑的基础上优化了内存使用,更加高效。
哲学和编程思想
优化代码不仅仅是一个技术问题,也涉及到一些哲学和编程思想。这些思想可以帮助我们更好地理解为什么进行某些优化,以及如何进行有效的优化。以下是这段代码优化涉及到的主要哲学和编程思想:
1. 空间-时间权衡(Space-Time Tradeoff)
- 哲学:在计算中,有时我们可以用更多的空间来换取更少的时间,反之亦然。这种权衡是许多优化决策的核心。
- 应用:在这段代码中,通过使用两个固定大小的列表(current 和 next_cards),我们避免了每次洗牌都创建新的列表,从而节省了时间。
2. 内存重用(Memory Reuse)
- 哲学:重用现有的资源而不是频繁分配和释放资源,可以提高效率和性能。
- 应用:我们通过交换 current 和 next_cards 的引用来重用内存,而不是每次都创建新的列表。这减少了内存分配的开销,提高了性能。
3. 最小化副作用(Minimize Side Effects)
- 哲学:减少函数或过程对外部状态的影响,有助于提高代码的可维护性和可预测性。
- 应用:通过交换引用而不是复制列表,我们有效地将副作用限制在局部变量内部。这使得代码更容易理解和维护。
4. 简洁性(Simplicity)
- 哲学:简洁的代码通常更容易理解、维护和调试。复杂性应尽可能地被消除。
- 应用:代码结构清晰,逻辑明确,没有不必要的操作,保持了代码的简洁性。
5. 局部性原理(Principle of Locality)
- 哲学:程序应尽量在局部范围内操作数据,以提高缓存效率和性能。
- 应用:通过使用固定大小的列表,我们确保操作是局部的,数据在内存中的位置相对固定,从而提高了缓存命中率。
6. KISS 原则(Keep It Simple, Stupid)
- 哲学:代码应保持尽可能简单,不引入不必要的复杂性。
- 应用:通过简单地交换列表的引用,而不是复杂的复制和分配操作,我们遵循了 KISS 原则。
7. DRY 原则(Don't Repeat Yourself)
- 哲学:尽量避免代码重复,提取公共逻辑到单一的地方。
应用:在优化过程中,避免了重复分配和复制列表的操作,集中在一个简单的引用交换上。
举一反三
理解并应用这些哲学和编程思想可以帮助你在编程中举一反三,开发出更高效、可维护的代码。以下是一些技巧和建议,结合之前提到的哲学和编程思想,帮助你在实际编程中应用:
1. 空间-时间权衡(Space-Time Tradeoff)
技巧:
- 缓存计算结果:如果一个函数的计算结果会被多次使用,可以缓存结果以减少重复计算。
- 预处理数据:在数据处理前进行一些预处理,可能会增加一些空间开销,但可以显著减少后续处理时间。
示例:
def fibonacci(n, memo={}):
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
return memo[n]
2. 内存重用(Memory Reuse)
技巧:
- 池化对象:复用对象而不是频繁地创建和销毁它们,比如使用对象池。
- 就地修改:当安全和可行时,尽量就地修改数据而不是创建新的副本。
示例:
def in_place_reverse(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
3. 最小化副作用(Minimize Side Effects)
技巧:
- 函数式编程:尽量编写纯函数(无副作用),避免修改全局状态。
- 局部变量:使用局部变量而不是全局变量或类变量来保存临时状态。
示例:
def pure_function(x, y):
return x + y
4. 简洁性(Simplicity)
技巧:
- 拆分函数:将复杂的函数拆分成多个小函数,每个函数只做一件事。
- 清晰命名:使用清晰、描述性的变量和函数名称,便于理解代码意图。
示例:
def process_data(data):
clean_data = clean(data)
transformed_data = transform(clean_data)
return transformed_data
def clean(data):
# 清理数据
pass
def transform(data):
# 转换数据
pass
5. 局部性原理(Principle of Locality)
技巧:
- 数据局部性:尽量让相关的数据在内存中存放在一起,减少缓存未命中。
- 代码局部性:尽量在一个局部范围内完成相关操作,减少跨模块调用。
示例:
def sum_of_squares(arr):
return sum(x * x for x in arr)
6. KISS 原则(Keep It Simple, Stupid)
技巧:
- 避免过度优化:在没有明确性能问题时,不要过早进行复杂的优化。
- 直观实现:优先选择直观、简单的实现方法,复杂的方法留作备选。
示例:
def calculate_area(radius):
return 3.14 * radius * radius # 简单计算圆的面积
7. DRY 原则(Don't Repeat Yourself)
技巧:
- 抽取公共代码:将重复出现的代码抽取到一个独立函数或模块中。
- 使用参数化:通过参数化来减少代码重复,增强代码的灵活性。
示例:
def calculate(operation, x, y):
if operation == 'add':
return x + y
elif operation == 'subtract':
return x - y
# 更多操作...
总结
通过理解和应用这些哲学和编程思想,可以帮助在编程中实现以下目标:
- 优化性能:通过合理的空间-时间权衡和内存重用,改进代码效率。
- 提高可维护性:通过最小化副作用、保持代码简洁和避免重复,增强代码的可读性和可维护性。
- 提高代码局部性:通过遵循局部性原理,改进代码运行效率和可预测性。
记住,这些技巧和思想不是独立的,而是相互关联和互补的。理解和实践这些思想不仅能提高你的编程能力,还能写出更加优雅和高效的代码。
感谢。