Python 中的 list 是一种有序的汇合,它对其中的元素的类型没什么要求,简直万物皆可放 list。这里探讨 list 的四个罕用操作:
- 如何从 list 中删除元素;
- list 的索引和切片是深拷贝还是浅拷贝;
- 两个 list 的交、并、差、对称差集;
- list 的排序办法。
1. 删除 list 中的某个元素
删除 list 中的某个元素,能够应用 del 和remove,del 是依照下标删除元素,remove 是依照值删除元素,只能删除匹配某值的第一个元素。
list1 = ['a','b','hello','world','hello'] # 依照下标删除元素
del list1[2]
print(list1)
['a', 'b', 'world', 'hello']
list1 = ['a','b','hello','world','hello']
list1.remove('hello') # 依照值删除元素,只能删除匹配某值的第一个元素
print(list1)
['a', 'b', 'world', 'hello']
如果想要删除 list 中所有的“hello”,不能像上面示意的办法应用 for 循环,否则如果 list 中有间断的“hello”会删除不掉。
list1 = ['a','b','hello','world','c','hello','hello','d']
for i in list1:
if i == 'hello':
list1.remove(i)
print(list1)
['a', 'b', 'world', 'c', 'hello', 'd']
从后果上看,在 for 循环时,当 remove 删除一个匹配的元素后,i 曾经指向了下一个元素(这和 C 语言里 vector 的迭代器一样),所以如果遇到间断两个“hello”,i 就跳过了第二个“hello”。验证如下,after remove 后 i 的值产生了变动:
list1 = ['a','b','hello','world','c','hello','hello','d']
for i in list1:
print('current item :'+i)
if i == 'hello':
list1.remove(i)
print('after remove :'+i)
print(list1)
current item : a
current item : b
current item : hello
after remove : hello
current item : c
current item : hello
after remove : hello
current item : d
['a', 'b', 'world', 'c', 'hello', 'd']
如果须要删除 list 中所有匹配的元素,能够做一个 list 的 深拷贝 用于遍历,而原 list 用于删除元素,示例如下:
from copy import deepcopy
list1 = ['a','b','hello','world','c','hello','hello','d']
list2 = deepcopy(list1)
for i in list2:
print('current item :'+i)
if i == 'hello':
list1.remove(i)
print('after remove :'+i)
print(list1)
current item : a
current item : b
current item : hello
after remove : hello
current item : world
current item : c
current item : hello
after remove : hello
current item : hello
after remove : hello
current item : d
['a', 'b', 'world', 'c', 'd']
2. list 的切片和索引是深拷贝还是浅拷贝?
上例提到了 list 的深拷贝,那么 list 的切片和索引是深拷贝还是浅拷贝?
首先看一下 list 的索引和切片的根本应用办法:Python 中 list 的索引能够是正数,正数是逆序,逆序从 - 1 开始。
# 下标索引和切片
list1 = ['a','b','c','d']
print(list1[0])
print(list1[1:3])
print(list1[-4:-3])
a
['b', 'c']
['a']
list 的索引或者切片是深拷贝还是浅拷贝? 这里须要用到一个 id 办法,它可能给出对象的内存地址。
Python 中对 list 的复制,其实是复制了 list 的援用,原对象和新对象会指向同一块内存地址。扭转其中一个 list 对象中的元素的值,另一个也会被扭转。如下所示,list1 和 list2 理论指向了同一个内存地址,所以一旦扭转 list2 中的元素的值,list1 也被扭转了。
list1 = ['a','b','c']
list2 = list1
print(id(list1))
print(id(list2))
list2[1] = 'd'
print(list1)
140356459153200
140356459153200
['a', 'd', 'c']
想要 list1 和 list2 互不相干,一种解决办法是 应用切片的办法复制原对象,这样失去的 list2 的内存地址的确不一样了,扭转 list2 中元素的值,list1 不会扭转。
# 应用切片的办法复制
list1 = ['a','b','c']
list2 = list1[:]
print(id(list1))
print(id(list2))
list2[1] = 'd'
print(list1)
140356987974432
140356459153040
['a', 'b', 'c']
可是这样就万事无忧了吗?如果 list 中的对象是个简单的构造,比方也是个 list,应用切片复制的形式有没有问题呢?
list1 = [['a','b'],['c','d']]
list2 = list1[:]
print(id(list1))
print(id(list2))
list2[1][1] = 'x'
print(list1)
140356987975872
140356458496720
[['a', 'b'], ['c', 'x']]
如果遇到嵌套列表(二维数组), 即便应用切片的办法复制了 list2,批改 list2 中的元素,list1 还是会被改掉。因为 list 中的元素如 list1[0],是个 list,是个对象,也是援用,如果查看它俩的内存地址,会发现其实是一样的。
list1 = [['a','b'],['c','d']]
list2 = list1[:]
print(id(list1[0]))
print(id(list2[0]))
140356717561408
140356717561408
所以,当 list 中是对象时,切片后批改元素会扭转原来的 list 中的值,保险的方法是用 深拷贝。
from copy import deepcopy
list1 = [['a','b'],['c','d']]
list2 = deepcopy(list1)
print('list 的内存地址:')
print(id(list1))
print(id(list2))
print('list[0] 的内存地址:')
print(id(list1[0]))
print(id(list2[0]))
list2[1][1] = 'x'
print(list1)
list 的内存地址:140356987985824
140356987984384
list[0] 的内存地址:140356459155120
140356451242944
[['a', 'b'], ['c', 'd']]
3. list 的交、并、差、对称差集
这也是一个较为常见的问题,给出两个 list,要求它们的交加、并集、差集、对称差集。这里给出几种办法,并比拟性能。两个 list 如下:
list1 = ['hello','world','day','night','world']
list2 = ['day','hello','spring']
首先是求 交加,即找出既在 list1 中呈现,也在 list2 中呈现的元素。这里给出三种写法,前两种借助 set 来实现(举荐),后一种是 list 遍历办法。借助 set 办法的话,如果原 list 中有多个雷同的元素,将不会保留多份,list 中元素的程序也不再保留。
# 交加
list3 = list(set(list1) & set(list2))
print(list3)
list4 = list(set(list1).intersection(set(list2)))
print(list4)
list5 = [x for x in list1 if x in list2]
print(list5)
['hello', 'day']
['hello', 'day']
['hello', 'day']
求 list并集,即在 list1 中或者在 list2 中呈现的元素。
# 并集
list3 = list(set(list1) | set(list2))
print(list3)
list4 = list(set(list1).union(set(list2)))
print(list4)
list5 = list(set(list1 + list2))
print(list5)
['night', 'day', 'spring', 'hello', 'world']
['night', 'day', 'spring', 'hello', 'world']
['day', 'spring', 'night', 'hello', 'world']
求 list 的 差集,即在 list1 中呈现,但不在 list2 中的元素
# 差集
list3 = list(set(list1).difference(set(list2)))
print(list3)
list4 = list(set(list1)-(set(list2)))
print(list4)
# 不求惟一 放弃程序
list5 = [x for x in list1 if x not in list2]
print(list5)
['night', 'world']
['night', 'world']
['world', 'night', 'world']
求 list 的 对称差集,只属于 list1 的元素和只属于 list2 的元素
# 对称差集
list3 = list(set(list1).symmetric_difference(set(list2)))
print(list3)
list4 = list(set(list1)^(set(list2)))
print(list4)
# 不求惟一 放弃程序
list5 = [x for x in list1 if x not in list2] + [x for x in list2 if x not in list1]
print(list5)
['night', 'world', 'spring']
['night', 'world', 'spring']
['world', 'night', 'world', 'spring']
性能方面,因为 set 外部有哈希表,所以远高于只用 list 解决,set 的两种写法性能差别不大。这里做一个小试验,list1 和 list2 都是有 10 万数字的 list,用不同的办法求解其交加,并用 time 计时。在这个数量级上,仅用 list 办法性能较慢,所以如果不要求后果保留所有元素并放弃原程序,借用 set 是更举荐的办法。
import random
list1 = []
list2 = []
for i in range(100000):
n = random.randint(0, 100000)
list1.append(n)
m = random.randint(5000, 105000)
list2.append(m)
%%time
# 交加 1
list3 = list(set(list1) & set(list2))
CPU times: user 26.4 ms, sys: 1.86 ms, total: 28.2 ms
Wall time: 27.6 ms
%%time
# 交加 2
list4 = list(set(list1).intersection(set(list2)))
CPU times: user 33.5 ms, sys: 1.17 ms, total: 34.7 ms
Wall time: 34 ms
%%time
# 交加 3
list5 = [x for x in list1 if x in list2]
CPU times: user 2min 20s, sys: 243 ms, total: 2min 20s
Wall time: 2min 20s
4. list 的排序操作
list 的排序办法能够应用内置的 sort 和 sorted,sorted 有返回值,返回排序后的列表;sort 是扭转 list 自身的程序,无返回值。
sorted 办法
list1 = [5, 2, 3, 1, 4]
list2 = sorted(list1)
print(list2)
[1, 2, 3, 4, 5]
sort 办法
list1 = [5, 2, 3, 1, 4]
list1.sort()
print(list1)
[1, 2, 3, 4, 5]
还能够在排序时通过 key 参数来指定一个函数用于计算待比拟的值,此函数将在每个元素比拟前被调用,所以简单的对象的 list,能够通过指定 key 来排序。比方按某一个重量排序,按某一个重量的长度排序等等。
list1 = [[1,'c','hello'],[2,'a','morning'],[3,'a','cat']]
# 按元素中的某一重量排序
list1.sort(key=lambda x:x[1])
print(list1)
[[2, 'a', 'morning'], [3, 'a', 'cat'], [1, 'c', 'hello']]
# 按元素的某一个重量的函数值排序
list1.sort(key=lambda x:len(x[2]))
print(list1)
[[3, 'a', 'cat'], [1, 'c', 'hello'], [2, 'a', 'morning']]
注:排序后果是稳固的,要害 key 雷同时,先呈现在 list 中的元素在排序后果中也在后面。
如果 list 中的元素是某个 class 的对象,还能够通过 itemgetter、attrgetter 获取元素或者对象的属性,再排序。示例如下,如果 list 的元素自身是能够按下标索引的(例如嵌套 list),能够应用 itemgetter 取得重量。
from operator import itemgetter
list1 = [[1,'c','hello'],[2,'a','morning'],[3,'b','cat']]
# 对能够应用下标索引的 如按第 1 个重量排序
list1.sort(key=itemgetter(1))
print(list1)
[[1, 'a', 'morning'], [3, 'b', 'cat'], [1, 'c', 'hello']]
如果 list 中是简单的 class 对象,能够用 attrgetter 依照属性名字获取属性的值,并按此排序。举例说明,先创立一个 Person 对象的 list:
class Person:
def __init__(self, name, age, work):
self.name = name
self.age = age
self.work = work
def __repr__(self):
return repr((self.name, self.age, self.work))
list1 = [Person('赵赵',45,'月亮中学'), Person('李李', 20, '宇宙电子厂'),Person('王王', 35, '宇宙电子厂')]
而后依照 Person 的 age 属性排序
from operator import attrgetter
# 对对象的某个属性排序
list2 = [Person('赵赵',45,'月亮中学'), Person('李李', 20, '宇宙电子厂'),Person('王王', 35, '宇宙电子厂')]
list2.sort(key=attrgetter('age'))
print(list2)
[('李李', 20, '宇宙电子厂'), ('王王', 35, '宇宙电子厂'), ('赵赵', 45, '月亮中学')]
itemgetter、attrgetter 更不便的一点是反对 多级排序,即能够传入多个 key,先按第一个 key 排序,第一个 key 雷同的,再按第二个 key 排序。
# 先按第 0 个元素排序,再按第 1 个元素排序
list1 = [[1,'c','hello'],[1, 'a','morning'],[3, 'b','cat']]
list1.sort(key=itemgetter(0,1))
print(list1)
[[1, 'a', 'morning'], [1, 'c', 'hello'], [3, 'b', 'cat']]
# 先按 work 排序,再按 age 排序
list2 = [Person('赵赵',45,'月亮中学'), Person('李李', 20, '宇宙电子厂'),Person('王王', 35, '宇宙电子厂')]
list2.sort(key=attrgetter('work','age'))
print(list2)
[('李李', 20, '宇宙电子厂'), ('王王', 35, '宇宙电子厂'), ('赵赵', 45, '月亮中学')]
小结
本文探讨 list 的四个罕用操作:1. 如何从 list 中平安的删除元素;2. 当 list 中是简单构造对象时,切片和索引不是深拷贝;3. 借用 set 求解两个 list 的交、并、差、对称差集;4. list 的多种排序办法。
我的 Python 版本
>>> import sys
>>> print(sys.version)
3.7.6 (default, Jan 8 2020, 13:42:34)
[Clang 4.0.1 (tags/RELEASE_401/final)]