python生成器→杨辉三角→深浅拷贝

实训需要,最近一直都在学python,刚起步吧,很多没接触到的语言新特性觉得蛮新奇的。然后在看生成器的时候发现了这个问题,在此记录下。

杨辉三角:

《python生成器→杨辉三角→深浅拷贝》

题意:

廖雪峰老师有一道题是:
把杨辉三角每一行看做一个list,试写一个generator,不断输出下一行的list:

# 期待输出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
n = 0
results = []
for t in triangles():
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break
if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('测试通过!')
else:
    print('测试失败!')

我们要实现的就是triangles()这个生成器。

不完美代码:

def triangles():
    L = [1]
    while True:
        yield L
        L.append(0)
        L = [L[i - 1] + L[i] for i in range(len(L))]

解析:

1、杨辉三角第一行只有一个1,所以先建立一个L = [1],第一次调用的时候直接返回L即可。
2、第二行及以后的思路,根据杨辉三角的特点“每行每个数字等于上一行该位置的相邻两个数字之和”,所以对每一行都可以用

newList[0] = L[i – 1] + L[i]

这种逻辑去处理,而且不需要用一个大list去装载每一行的list,可以每次都用列表生成式重构这个list。
那么问题来了,边界的1咋办,它们上一行没有两个相邻数字,这里列出两种方法:
①、先算出除了首尾的1以外的所有数,最后再在首尾补1即可。
②、没有条件就创造条件,给上一行首尾补0。
即比如[1, 2, 1]可以看成[0, 1, 2, 1, 0],
这样下一行就为[1, 3, 3, 1]。
python中list[-1]是倒数第一个元素,对newList[0] = list[-1] + list[0]处理的时候完全不用担心list[-1]边界溢出的问题,所以完全可以把第一个0删掉,这样就是每算一行list,就在该行list末尾补0。而且巧妙的是,杨辉三角下一行确实比上一行要只多一个数。这就是L.append(0)的原因了。

总结一下代码的思路:生成器每次都从yield L语句后继续运行,所以我们对L末尾补0,这样新的L长度刚好是新的一行杨辉三角的长度,在range(len(L))这个长度范围内对每个数字都进行newL[i]=L[i – 1] + L[i]处理即可。

改进与思考:

问题1:

L = [L[i – 1] + L[i] for i in range(len(L))]
这行代码在运行的时候,例如 L = [1,2,1,0]生成[1,3,3,1],生成第一个3之后,原来的2应该被覆盖了。
答:我觉得应该是[L[i – 1] + L[i] for i in range(len(L))]这里面会隐式地生成一个newList,这个newList内容与L一模一样,是L的深拷贝。所以不会立即改动到L,然后等代码逻辑全跑完才返回该newList赋值给L,此时L才发生改变。

问题2:

不完美代码运行,显示的是对的,但是测试显示失败。

《python生成器→杨辉三角→深浅拷贝》

看上面测试的代码,输出“测试失败”也就是result的值是错的。
输出results一看,果然,results每一行末尾都带着一个0。
[[1, 0],
[1, 1, 0],
[1, 2, 1, 0],
[1, 3, 3, 1, 0],
[1, 4, 6, 4, 1, 0],
[1, 5, 10, 10, 5, 1, 0],
[1, 6, 15, 20, 15, 6, 1, 0],
[1, 7, 21, 35, 35, 21, 7, 1, 0],
[1, 8, 28, 56, 70, 56, 28, 8, 1, 0],
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]]
然而最后一行末尾没有带0,所以回去看triangles生成器的代码:

for t in triangles():
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break

每次print(t)的时候t还是正确的,但是执行results.append(t)的时候,由于results.append(t)的参数t是引用传递,所以此时results这个大list中的小list和t指向的List是同一个地址。
而我们再次执行到for t in triangles():的时候,triangles()里面从yield L处继续运行,马上就会执行L.append(0),因而使得刚才的t指向的List末尾多了一个0。
因此不完美代码还是有瑕疵的,应该怎么修改呢?

完美代码:

①、利用浅拷贝,改为

def triangles():
    L = [1]
    while True:
        yield L
        L = L.copy()
        L.append(0)
        L = [L[i - 1] + L[i] for i in range(len(L))]

②、可以把L.append(0)改成L = L + [0],L.append(0)是直接在原来的地址指向的L的末尾加0,而 L + [0]只对当前变量修改,不会修改到原来的L。

def triangles():
    L = [1]
    while True:
        yield L
        L = L + [0]
        L = [L[i - 1] + L[i] for i in range(len(L))]

深拷贝与浅拷贝:

区别

  • 直接赋值(等号):其实就是对象的引用(别名)。
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象(子对象仍然是引用状态)。
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象(可以尽情修改也不会影响到原来的)。

示意图

赋值引用

b = a: 赋值引用,a 和 b 都指向同一个对象。
《python生成器→杨辉三角→深浅拷贝》

浅拷贝

b = a.copy(): 浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。
《python生成器→杨辉三角→深浅拷贝》

深拷贝

b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。
《python生成器→杨辉三角→深浅拷贝》

实例:

#!/usr/bin/python
# -*-coding:utf-8 -*-

import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象

b = a                       #赋值,传对象的引用
c = copy.copy(a)            #对象拷贝,浅拷贝
d = copy.deepcopy(a)        #对象拷贝,深拷贝

a.append(5)                 #修改对象a
a[4].append('c')            #修改对象a中的子数组对象

print( 'a = ', a )
print( 'b = ', b )
print( 'c = ', c )
print( 'd = ', d )

输出为

(‘a = ‘, [1, 2, 3, 4, [‘a’, ‘b’, ‘c’], 5])
(‘b = ‘, [1, 2, 3, 4, [‘a’, ‘b’, ‘c’], 5])
(‘c = ‘, [1, 2, 3, 4, [‘a’, ‘b’, ‘c’]])
(‘d = ‘, [1, 2, 3, 4, [‘a’, ‘b’]])

    原文作者:杨辉三角问题
    原文地址: https://blog.csdn.net/A657997301/article/details/79776097
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞