6、Python3 列表应用

作者: Brinnatt 分类: python 术 发布时间: 2023-03-30 09:59
  • 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。
  • Python有6个序列的内置类型,但最常见的是列表和元组。
  • 序列都可以进行的操作包括索引,切片,加,乘,检查成员。
  • 此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法。
  • 列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现。
  • 列表的数据项不需要具有相同的类型。
  • 创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。

如下所示:

list1 = ['Google', 'Runoob', 1997, 2000];
list2 = [1, 2, 3, 4, 5 ];
list3 = ["a", "b", "c", "d"];

与字符串的索引一样,列表索引从0开始。列表可以进行截取、组合等。

6.1、访问列表中的值

使用下标索引来访问列表中的值,同样你也可以使用方括号的形式截取字符,如下所示:

#!/usr/bin/python3

list1 = ['Google', 'Runoob', 1997, 2000];
list2 = [1, 2, 3, 4, 5, 6, 7 ];

print ("list1[0]: ", list1[0])
print ("list2[1:5]: ", list2[1:5])

以上实例输出结果:

list1[0]:  Google
list2[1:5]:  [2, 3, 4, 5]

6.2、更新列表

你可以对列表的数据项进行修改或更新,你也可以使用append()方法来添加列表项,如下所示:

#!/usr/bin/python3

list = ['Google', 'Runoob', 1997, 2000]

print ("第三个元素为 : ", list[2])
list[2] = 2001
print ("更新后的第三个元素为 : ", list[2])

注意:我们会在接下来的章节讨论append()方法的使用

以上实例输出结果:

第三个元素为 :  1997
更新后的第三个元素为 :  2001

6.3、删除列表元素

可以使用 del 语句来删除列表的的元素,如下实例:

#!/usr/bin/python3

list = ['Google', 'Runoob', 1997, 2000]

print ("原始列表 : ", list)
del list[2]
print ("删除第三个元素 : ", list)

以上实例输出结果:

原始列表 :  ['Google', 'Runoob', 1997, 2000]
删除第三个元素 :  ['Google', 'Runoob', 2000]

注意:我们会在接下来的章节讨论 remove() 方法的使用

6.4、Python列表脚本操作符

列表对 + 和 * 的操作符与字符串相似。+ 号用于组合列表,* 号用于重复列表。

如下所示:

Python 表达式 结果 描述
len([1, 2, 3]) 3 长度
[1, 2, 3] + [4, 5, 6] [1, 2, 3, 4, 5, 6] 组合
['Hi!'] * 4 ['Hi!', 'Hi!', 'Hi!', 'Hi!'] 重复
3 in [1, 2, 3] True 元素是否存在于列表中
for x in [1, 2, 3]: print(x, end=" ") 1 2 3 迭代

6.5、Python列表截取与拼接

Python的列表截取与字符串操作类型,如下所示:

L=['Google', 'Runoob', 'Taobao']

操作:

Python 表达式 结果 描述
L[2] 'Taobao' 读取第三个元素
L[-2] 'Runoob' 从右侧开始读取倒数第二个元素: count from the right
L[1:] ['Runoob', 'Taobao'] 输出从第二个元素开始后的所有元素
>>>L=['Google', 'Runoob', 'Taobao']
>>> L[2]
'Taobao'
>>> L[-2]
'Runoob'
>>> L[1:]
['Runoob', 'Taobao']
>>>

列表还支持拼接操作:

>>>squares = [1, 4, 9, 16, 25]
>>> squares += [36, 49, 64, 81, 100]
>>> squares
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>>

6.6、嵌套列表

使用嵌套列表即在列表里创建其它列表,例如:

>>>a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
>>> x[0]
['a', 'b', 'c']
>>> x[0][1]
'b'

6.7、Python列表函数&方法

Python包含以下函数:

序号 函数 描述
1 len(list) 列表元素个数
2 max(list) 返回列表元素最大值
3 min(list) 返回列表元素最小值
4 list(seq) 将元组转换为列表

Python包含以下方法:

序号 方法 描述
1 list.append(obj) 在列表末尾添加新的对象
2 list.count(obj) 统计某个元素在列表中出现的次数
3 list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)
4 list.index(obj) 从列表中找出某个值第一个匹配项的索引位置
5 list.insert(index, obj) 将对象插入列表
6 list.pop([index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值
7 list.remove(obj) 移除列表中某个值的第一个匹配项
8 list.reverse() 反向列表中元素
9 list.sort( key=None, reverse=False) 对原列表进行排序
10 list.clear() 清空列表
11 list.copy() 复制列表

6.8、思考与总结

x = [[1,2,3]]*3
print(x)
x[0][1] = 20
print(x)

y = [1]*5
y[0] = 6
y[1] = 7
print(y)
上面代码运行结果是什么?为什么?

输出:

[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
[[1, 20, 3], [1, 20, 3], [1, 20, 3]]
[6, 7, 1, 1, 1]

总结:

如果列表里面是复杂类型的值,那这个值很有可能是一个指针引用,修改这个值中间的某个元素,会改变其它的引用值

lst0 = list(range(4))
lst5 = lst0.copy()
print(lst5 == lst0)
lst5[2] = 10
print(lst5 == lst0)

输出:
True
False
lst0 = [1, [2, 3, 4], 5]
lst5 = lst0.copy()
lst5 == lst0
lst5[2] = 10
lst5 == lst0
lst5[2] = 5
lst5[1][1] = 20
lst5 == lst0

输出:
True
copy模块提供了deepcopy
import copy
lst0 = [1, [2, 3, 4], 5]
lst5 = copy.deepcopy(lst0)
lst5[1][1] = 20
lst5 == lst0

输出:
False

结论:通过对比,我们要引入一个概念,叫深浅拷贝,上述对比实验就是深浅拷贝,浅拷贝实际上相当于复制了一个指针,实际数据并没有复制,所以改或没改一定要落实到内存中数据本身,追踪内存中最本质的数据,才能运筹帷幄。那么,深拷贝就不言而喻了,指把数据本身也复制一份。

6.9、实例

6.9.1、求素数

用列表的方式求100000内的素数,基于的原理是“一个合数一定可以分解成几个素数的乘积,也就是说,一个数如果能被一个素数整除就是合数”

import datetime
start = datetime.datetime.now()
lst = [2]
count = 1
for i in range(3,100000,2):
    up = int(i**0.5 + 1)
    for j in lst:
        if i % j == 0:
            flag = False
            break
        if j >= up:
            flag = True
            break
    if flag:
        lst.append(i)
        count += 1
print(count)
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)

6.9.2、杨辉三角实现

杨辉三角

方法1:下一行依赖上一行所有元素,是上一行所有元素的两两相加的和,再在两头各加1

triangle = [[1],[1,1]]
n = int(input('please input line number: '))
for i in range(2,n):
    cur = [1] # 当前需要拼接的行
    pre = triangle[i-1] # 找到上一行
    for j in range(len(pre)-1):
        cur.append(pre[j]+pre[j+1]) # 相比较下面一种,cur作为临时列表,拼接完成后加入triangle
    cur.append(1)
    triangle.append(cur)
print(triangle)

方法1变种:下一行依赖上一行所有元素,是上一行所有元素的两两相加的和,再在两头各加1

triangle=[]
n = 6
for i in range(n):
    row = [1]
    triangle.append(row) # 这种方式的精髓就在于row作为一个复杂类型的变量嵌入列表中,row变化,列表随之变化
    if i==0:
        continue
    for j in range(i-1):
        row.append(triangle[i-1][j]+triangle[i-1][j+1]) # 所以数字追加直接在列表嵌套当中完成
    row.append(1)
print(triangle)

方法2:除了第一行以外,每一行每个元素(包括1在内)都由上一行的元素相加得到。如何得到两头的1呢?目标是打印指定的行,所以算出一行就打印一行,不需要用一个大空间存储所有已经算出的行。

n = int(input('please input line number: '))
newline = [1]
print(newline)

for i in range(1,n):
    oldline = newline.copy()    # 浅拷贝
    oldline.append(0)   # 在尾部加一个0,利用正负索引获取该0,算出第一个和最后一个1
    newline.clear()     # 以newline为容器算出当前行,算前清空

    offset = 0  # 初始化索引计算器
    while offset <= i:
        newline.append(oldline[offset-1]+oldline[offset]) # oldline是上一行列表,拼出当前行列表
        offset += 1
    print(newline)

输出:
please input line number: 4
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]

方法2变种:除了第一行以外,每一行每个元素(包括1在内)都由上一行的元素相加得到。如何得到两头的1呢?目标是打印指定的行,所以算出一行就打印一行,不需要用一个大空间存储所有已经算出的行。

n = 6
newline = [1]
print(newline)

for i in range(1,n):
    oldline = newline.copy()    # 浅拷贝
    oldline.append(0)   # 在尾部加一个0,利用正负索引获取该0,算出第一个和最后一个1
    newline.clear()     # 以newline为容器算出当前行,算前清空

    for j in range(i+1):
        newline.append(oldline[j-1] + oldline[j])   # 当j为0时oldline[-1]索取0这一点比较巧妙;j>0时都是正索引,拼接当前行时好理解
    print(newline)

输出:
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]

方法3:前面2种在算法上不够优秀,主要是按部就班的方式拼凑,第3种方法主要有两个优化点

  • 一次性开辟刚刚好的空间,利用乘法“*”一次性开辟,相较于append一个个追加要高效很多
  • 利用对称性,只算一半,在算法高效上毋庸置疑
triangle = []
n = 6
for i in range(n):
    row = [1]*(i+1)     # i是从0开始的
    triangle.append(row)    # 精髓就在于row是一个引用类型,row改变,triangle中追加的row也会改变
# 循环的次数可以在草稿纸上画出来
# i == 0时,第1行,不需对称,无中点,不循环
# i == 1时,第2行,不需对称,无中点,不循环
# i == 2时,第3行,不需对称,有中点,循环1次    j == 1
# i == 3时,第4行,需要对称,无中点,循环1次
# i == 4时,第5行,需要对称,有中点,循环2次    j == 2
# i == 5时,第6行,需要对称,无中点,循环2次
    for j in range(1,i//2+1):   # i=2第三行才能进来,循环次数确定了
        val = triangle[i-1][j-1] + triangle[i-1][j]
        row[j] = val
        if i != 2*j:    # 根据上面找规律分析,当 i == 2*j 时,正好是中点,中点没有对称点,所以要跳过
            row[-j-1] = val # 对称点赋相同的值
print(triangle)

方法4:在方法3的基础上再进一步优化

  • 只开辟一个列表实现
n = 6
row = [1]*n # 只开辟一次空间

for i in range(n):
    offset = n - i  # 对称填充时,从右向左定位用到的偏移量
    z = 1   # 用临时变量记录将要被覆盖的当前数值

    for j in range(1,i//2+1):   # i = 2时,也就是计算第三行时,才能进来
        val = z + row[j]    # 算出将要替换j位的数值
        row[j], z = val, row[j] # 替换j位数值,同时将j位原始的数值赋给z,用作计算下一位的值
        if i != 2*j:    # 只有奇数行才会出现 i == 2*j,这个位置正好是中间点,而中间点是没有对称点的,所以要跳过
            row[-j-offset] = val    # 每计算出一个左对称点,就找到右对称点进行对称赋值
    print(row[:i+1])    # 每一个 i 被 j 循环完,就可以截取 i + 1 个刚拼好的杨辉三角序列

6.9.3、杨辉三角n行k列求值

方法1:计算到m行,打印出k项

# 求m行k个元素
# m行元素有m个,所以k不能大于m
# 这个需求需要保存m行的数据,那么可以使用一个嵌套结构[[],[],[]]
m = 5
k = 4
triangle = []
for i in range(m):
    # 所有行都需要1开头
    row = [1]
    triangle.append(row)
    if i == 0:
        continue
    for j in range(1,i):
        row.append(triangle[i-1][j-1] + triangle[i-1][j])
    row.append(1)
print(triangle)
print("---------")
print(triangle[m-1][k-1])
print("---------")

测试效率:371 µs ± 8.43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

方法2:根据杨辉三角的定理:第n行的m个数(m > 0 且 n > 0)可表示为C(n-1,m-1),即为从n-1个不同元素中取m-1个元素的组合数。

# 组合数公式:有m个不同元素,任意取n(n <= m)个元素,记作C(m,n),组合数公式为:C(m,n) = m!/(n!(m-n)!)

# m 行 k 列的值,C(m-1,k-1)组合数
m = 9
k = 5
n = m - 1
r = k - 1
d = n - r
# C(m-1,k-1) = n!/(r!d!)
targets = []
factorial = 1
for i in range(1,n+1):
    factorial *= i
    if i == r:
        targets.append(factorial)
    if i == d:
        targets.append(factorial)
    if i == n:
        targets.append(factorial)
# print(targets)
print(targets[2]//(targets[0]*targets[1]))

测试效率:88 µs ± 2.57 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

注:i == r、 i==n、 i==d 这三个条件不要写在一起,因为他们有可能两两相等。
算法说明:一趟到 n 的阶乘算出所有阶乘值。

6.9.4、排序

6.9.4.1、冒泡法排序

num_list = [[1,9,8,5,6,7,4,3,2],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,9,8],[9,8,7,6,5,4,3,2,1]]
nums = num_list[2]

print(nums)

length = len(nums)
count_swap = 0
count = 0

for i in range(length):
    flag = False
    for j in range(length-i-1):
        count += 1
        if nums[j] > nums[j+1]:
            tmp = nums[j]
            nums[j] = nums[j+1]
            nums[j+1] = tmp
            flag = True
            count_swap += 1
    if not flag:    # 目前冒泡排序优化点只能是刚刚好升序或者降序,也就是不发生交换,直接break出去
        break
print(nums, count_swap, count)

6.9.4.2、选择排序

m_list = [[1,9,8,5,6,7,4,3,2],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,9,8],[9,8,7,6,5,4,3,2,1]]
nums = m_list[1]
length = len(nums)
print(nums)

count_swap = 0
count_iter = 0

for i in range(length):
    maxindex = i
    for j in range(i+1,length):
        count_iter += 1
        if nums[maxindex] < nums[j]:
            maxindex = j
    if i != maxindex:
        nums[i],nums[maxindex] = nums[maxindex],nums[i]
        count_swap += 1
print(nums, count_swap, count_iter)

6.9.4.3、插入排序

m_list = [[1,9,8,5,6,7,4,3,2],[1,2,3,4,5,6,7,8,9],[1,2,3,4,5,6,7,9,8],[9,8,7,6,5,4,3,2,1]]
s_list = m_list[0]
sc_list = [0] + s_list # 待排序列最前面加一个位置给哨兵,这个位置必须要有,j -= 1 不能变成负值
count_swap = 0
count_iter = 0
for i in range(2,len(sc_list)): # 必须从待排序列的第2个数开始
    count_iter += 1
    sc_list[0] = sc_list[i] # 将循环到的数提到哨兵位置
    j = i - 1 # 从循环到的数前一位开始一一跟哨兵比较
    if sc_list[j] > sc_list[0]: # 加上这个判断,哨兵不用赋值到原来的位置上,多此一举
        while sc_list[j] > sc_list[0]:
            sc_list[j+1] = sc_list[j] # 比哨兵大,就右移
            j -= 1
            count_swap += 1
        sc_list[j+1] = sc_list[0] # 不比哨兵大,哨兵插入此值右边
print(sc_list[1:])
print(count_iter)
print(count_swap)

6.9.5、转置矩阵方阵

有一个方阵,左边方阵,求其转置矩阵

1 2 3       1 4 7
4 5 6 ==>   2 5 8
7 8 9       3 6 9

规律:对角线不动,a[i][j] <=> a[j][i],而且到了对角线,就停止,去做下一行,对角线上的元素不动。
# 定义一个方阵
# 1 2 3       1 4 7
# 4 5 6 ==>   2 5 8
# 7 8 9       3 6 9
matrix = [[1,2,3],[4,5,6],[7,8,9]]
print(matrix)
count = 0
for i, row in enumerate(matrix):
    for j, col in enumerate(row):
        if i < j: # 这个条件是问题的关键所在,为什么是 i < j,可以手稿分析一下
            temp = matrix[i][j]
            matrix[i][j] = matrix[j][i]
            matrix[j][i] = temp
            count += 1
print(matrix)
print(count)

输出结果:
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
3

方法2:

matrix = [[1,2,3],[4,5,6],[7,8,9]]
length = len(matrix)
count = 0
for i in range(length):
    for j in range(i): # 这个循环很关键,正好通过边界控制取对角线以下的值,不包括对角线值
        matrix[i][j],matrix[j][i] = matrix[j][i],matrix[i][j]
        count +=1
print(matrix)
print(count)

输出结果:
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
3

总结:对称矩阵的诀窍存在于对角线,以上两种都是基于该方法实现,第2种更优秀,只循环一半。

6.9.6、转置矩阵非方阵

1 2 3          1 4
4 5 6   <==>   2 5
               3 6

方法1:扫描matrix第一行,在tm的第一列从上至下附加,然后再第二列附加,举例,扫描第一行1,2,3,加入到tm第一列,扫描第二行4,5,6,追加到tm第二列

# 定义一个矩阵,不考虑稀疏矩阵
matrix = [[1,2,3],[4,5,6]]
tm = []
count = 0
for row in matrix:
    for i,col in enumerate(row):
        if len(tm) < i + 1:
            tm.append([])
        tm[i].append(col)
        count += 1
print(matrix)
print(tm)
print(count)

输出结果:
[[1, 2, 3], [4, 5, 6]]
[[1, 4], [2, 5], [3, 6]]
6

方法2:能否一次性开辟目标矩阵的内存空间?如果可以,原矩阵的元素直接移动到转置矩阵的对称坐标就行了

matrix = [[1,2,3],[4,5,6]]
tm = [[0 for col in range(len(matrix))] for row in range(len(matrix[0]))]
count = 0

for i,row in enumerate(tm):
    for j,col in enumerate(row):
        tm[i][j] = matrix[j][i] # 将matrix的所有元素搬到tm中
        count += 1
print(matrix)
print(tm)
print(count)

输出结果:
[[1, 2, 3], [4, 5, 6]]
[[1, 4], [2, 5], [3, 6]]
6

6.9.7、数字统计

随机产生10个数字 。

要求:

  • 每个数字取值范围[1,20];
  • 统计重复的数字有几个,分别是什么;
  • 统计不重复的数字有几个,分别是什么;
  • 举例:11,7,5,11,6,7,4,其中2个数字7和11重复了,3个数字4、5、6没有重复过。

思路:

  • 对于一个排序的序列,相等的数字会挨在一起。但是如果先排序,还是要花时间,能否不排序解决?
  • 例如 11,7,5,11,6,7,4,先拿出11,依次从第二个数字开始比较,发现 11 就把对应索引标记,这样一趟比较就知道 11 是否重复,哪些地方重复。
  • 第二趟使用 7 和其后数字依次比较,发现 7 就标记,当遇到以前比较过的 11 的位置的时候,其索引已经被标记为 1,直接跳过。
import random

nums = []
for _ in range(10):
    nums.append(random.randrange(21))

print("Origin numbers = {}".format(nums))
print()

length = len(nums)
samenums = []
diffnums = []
states = [0] * length # 记录不同的索引异同状态

for i in range(length):
    flag = False
    if states[i] == 1:
        continue
    for j in range(i+1,length):
        if states[j] == 1:
            continue
        if nums[i] == nums[j]:
            flag = True
            states[j] = 1
    if flag:
        samenums.append(nums[i])
        states[i] = 1
    else:
        diffnums.append(nums[i])

print("Same numbers = {1}, Counter = {0}".format(len(samenums),samenums))
print("Different numbers = {1}, Counter = {0}".format(len(diffnums),diffnums))
print(list(zip(states,nums)))

结果输出:
Origin numbers = [8, 16, 14, 6, 15, 16, 20, 15, 9, 15]

Same numbers = [16, 15], Counter = 2
Different numbers = [8, 14, 6, 20, 9], Counter = 5
[(0, 8), (1, 16), (0, 14), (0, 6), (1, 15), (1, 16), (0, 20), (1, 15), (0, 9), (1, 15)]
标签云