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,应用切片复制的形式有没有问题呢?
<code class="Python">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,是个对象,也是援用,如果查看它俩的内存地址,会发现其实是一样的。
<code class="Python">list1 = [['a','b'],['c','d']] list2 = list1[:] print(id(list1[0])) print(id(list2[0])) 140356717561408 140356717561408
所以,当list中是对象时,切片后批改元素会扭转原来的list中的值,保险的方法是用深拷贝。
<code class="Python">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如下:
<code class="Python">list1 = ['hello','world','day','night','world'] list2 = ['day','hello','spring']
首先是求交加,即找出既在list1中呈现,也在list2中呈现的元素。这里给出三种写法,前两种借助set来实现(举荐),后一种是list遍历办法。借助set办法的话,如果原list中有多个雷同的元素,将不会保留多份,list中元素的程序也不再保留。
<code class="Python"># 交加 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中呈现的元素。
<code class="Python"># 并集 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中的元素
<code class="Python"># 差集 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的元素
<code class="Python"># 对称差集 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是更举荐的办法。
<code class="Python">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办法
<code class="Python">list1 = [5, 2, 3, 1, 4] list2 = sorted(list1) print(list2) [1, 2, 3, 4, 5]
sort办法
<code class="Python">list1 = [5, 2, 3, 1, 4] list1.sort() print(list1) [1, 2, 3, 4, 5]
还能够在排序时通过key参数来指定一个函数用于计算待比拟的值,此函数将在每个元素比拟前被调用,所以简单的对象的list,能够通过指定key来排序。比方按某一个重量排序,按某一个重量的长度排序等等。
<code class="Python">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取得重量。
<code class="Python">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:
<code class="Python">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属性排序
<code class="Python">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排序。
<code class="Python"># 先按第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版本
<code class="Python">>>> import sys >>> print(sys.version) 3.7.6 (default, Jan 8 2020, 13:42:34) [Clang 4.0.1 (tags/RELEASE_401/final)]