Python 列表解析 例子 讲解 学习 list comprehension in python demo

原创,如转载请注明出处http://blog.csdn.net/menglongli/article/details/17450607

概念

首先说一下什么是列表解析:

基本概念就是一种语法结构,其存在是为了方便通过已有列表,去方便快速创建新的列表。

[expression for target1 in iterable1 [if condition1]
   for target1 in iterable2 [if condition2]
   for target1 in iterable3 [if condition3]
   for target1 in iterable4 [if condition4]
   ... .... ... .... ... ... ... ... ... ... ... ... ... ... .
  for targetN in iterableN [if conditionN]]



以上便是通用的表达式;


性能

这里说他速度快,主要是和写for循环相比。

列表解析的速度很快,因为循环是在C而不是在Python中执行。

我写了几行代码来进行一下比较,给大家一点直观的感受

比如现在给定一个小写字母的列表形如['a', 'b' , 'c',....'y', 'z', 'a' ,'b',.....]

生成如上列表的代码如下:

myAlphaList=map(chr,range(97,123))*10000

现在想要生成另外一个列表,里面的字母和上面相同,但是要求均为大写,为了实现这一小功能,我写了几个不同的函数,然后我会分别对其进行“跑分”看一下效果:

这里用来“跑分”的计时函数,如下:

from timeit import Timer
from functools import partial

def get_execution_time(function, *args, **kwargs):
    """Return the execution time of a function in seconds."""
    numberOfExecTime = kwargs.pop('numberOfExecTime', 1)
    return round(Timer(partial(function, *args, **kwargs))
                 .timeit(numberOfExecTime), 5)
下面分别是几个完成同样功能,即生成新的元素为大写的列表,但是实现方式有别的函数,代码如下:


def lstToUpperUsingRawFor(lst):
    newLst=[]
    length=len(lst)
    for i in range(length):
        newLst.append(lst[i].upper())
    return newLst

def lstToUpperUsingForeach(lst):
    newLst=[]
    for e in lst:
        newLst.append(e.upper())
    return newLst

def lstToUpperUsingMap(lst):
    newLst = map(str.upper, lst)
    return newLst

def lstToUpperUsingListComprehension(lst):
    newLst=[e.upper() for e in lst]
    return newLst

然后我们调用一下这四个函数,分别看一下运行的速度:

n=50
print 'for',\
get_execution_time(lstToUpperUsingRawFor, myAlphaList,numberOfExecTime=n)
print 'foreach',\
get_execution_time(lstToUpperUsingForeach, myAlphaList,numberOfExecTime=n)
print 'map',\
get_execution_time(lstToUpperUsingMap, myAlphaList,numberOfExecTime=n)
print 'listComprehension ',\
get_execution_time(lstToUpperUsingListComprehension, myAlphaList,numberOfExecTime=n)


可以看到结果如下:

for                3.34284
foreach            2.90361
map                1.88996
listComprehension  1.82715

由上到下,速度越来越快


在这里我想补充一下,在上述函数比较的过程中,我尽量保证控制变量。

这里的变量主要是指使用的upper()函数尽量相同,列表中的每一个元素因为是小写字符,将其转成大写有两种写法:一个是'a'.upper(),另一个是用str.upper('a')。在其他条件相同的情况下,调用str.upper('a')要比调用'a'.upper() 慢得多,而在使用map的时候,不得以使用了str.upper(),因为函数式编程的特点,需要指定一个函数给map。但是即使这样map仍要比for循环要快。

为了严谨,如果我们在列表解析的过程中使用 newLst=[str.upper(e) for e in lst] 来代替newLst=[e.upper() for e in lst] 的话,则列表解析的速度变慢:

for                3.36268
listComprehension  3.07987
foreach            2.96513
map                1.89186


其实上面的内容主要也想说明一个问题,就是其实Python列表解析也有很多需要注意的细节,想要写出高效的列表解析,也是需要多去琢磨的。


使用与学习

Demo1

先从基本的说起,

lst=range(1,11)
newLst=[e for e in lst]
print newLst
以上代码用来生成新列表,新列表中的元素和旧列表中的元素一样

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

这是最基本,那么如果我想要添加一些判断条件,则要在后面追加条件:

lst=range(1,11)
newLst=[e for e in lst  if e%2==0]
print newLst
#[2, 4, 6, 8, 10]


Demo2

又比如

lst=range(1,11)
newLst=[e for e in lst  if e<=3]
print newLst
#[1, 2, 3]
这就是比较基础的列表解析,当然我后面还会讲复杂的,但是有了这个,就可以做一些很cool的事情了,

Demo3

这个是我以前在网上看到有人写的仅用一行就完成的快速排序,至于谁是第一个写出这行代码的人,也已无从考证了,不过这代码确实很绚:

def QSort(lst):
    return [] if lst==[] else QSort([e for e in lst[1:] if e<=lst[0]])+[lst[0]]+QSort([e for e in lst[1:] if e>lst[0]])
当然,上面的代码肯定不是写得最好的快排,为了简化快排,这里每次都是用lst的第0个元素做划分,不过这确实展现了列表解析的应用,以及快排的核心思想,还有递归的思想。

让我们运行,看一下结果:

import random
lst=range(1,11)
random.shuffle(lst)
print lst
#[3, 7, 2, 9, 8, 5, 1, 4, 10, 6]
print QSort(lst)
#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

简要说明上述代码中的几个用到的概念:

首先上面的用到了 

return A if C else B

这其实是

if C:
    return A
else:
    return B

的另一种简便写法。

其次上面用到了切片的概念,即lst[1:],表示lst中从编号为1的元素起(从0开始),向后直到最后。完整的切片写法是:

lst[start:step:end]
复杂的切片(省略参数,倒序等),大家可以再另去学习,不再赘述。

还有一个对于新手来说经常容易出错的地方就是

QSort( ) +[ lst[0] ]+QSort( )
这里面一定要把用来划分的元素lst[0] 用[ ]列表封装起来,因为[ ]+[ ]+ [ ]是合法的,但是一个列表+数字是错误的。


Demo4

那么上面讲的例子都是只有一个变量比如:e

新列表中的每一个元素用e来表示 。那么e是哪里来的呢?for e in lst,原来是每一个lst里面的元素e。需要什么判断吗?那么后面追加比如 if e%2==0的判断

以上文字加黑,是因为每次我写列表解析的时候,都会这样边问自己,边写,这样基本不会出错,尤其是当遇到复杂的列表解析的时候。(多个变量)


lst1=['A','B','C','D']
lst2=['a','b','c','d']

如果我想要组合这两个列表,生成一一对应绑定在一起的新列表,即'Aa' 'Bb' 'Cc' 'Dd'应该怎么写list comprehension?

newLst=[e1+e2 for e1 in lst1 for e2 in lst2]
print newLst
#['Aa', 'Ab', 'Ac', 'Ad', 'Ba', 'Bb', 'Bc', 'Bd', ......]

我们发现,这个结果不是我们想要的,原来,对于两个变量,如果像上面这样,“并列”写两个for,其实并不是真正的“并列”,而是一种"nested"嵌套的。

所以像上面那样写两个for循环其实质相当于:

newLst=[]
for e1 in lst1:
    for e2 in lst2:
        newLst.append(e1+e2)

所以现在我们已经学会写嵌套的for循环对应的列表解析了 恭喜恭喜!(上面的问题,纯属为了引出这)

但是上面的问题终究还是要解决的,应该怎么写呢?

lst1=['A','B','C','D']
lst2=['a','b','c','d']
newLst=[e1+e2 for (e1,e2) in zip(lst1,lst2)]
print newLst
#['Aa', 'Bb', 'Cc', 'Dd']


OK,这样就搞定了,原来是将lst1,lst2通过zip生成一个内部元素一一对应的lst(长这个样子[('A', 'a'), ('B', 'b'), ('C', 'c'), ('D', 'd')]),然后写一个for,把e1,e2相应地提取出来,就可以了。


Demo5

接下来,再讲一种嵌套的列表解析会经常遇到的一种常用场景:

比如将一个二维列表平坦化

lst = [[1, 3, 5], [2, 4]]
print [flatten for inner in lst for flatten in inner]
#[1, 3, 5, 2, 4]

可以看到,这里面出现了两个参数,但是这两个参数flatten,inner可不像上面的e1,e2那么“对等”,这里面的两个变量本身也是“嵌套”的,

遇到这样的列表解析的时候,怎么读懂这段的代码?

新列表中的元素是什么?是flatten。 那么flatten是哪里来的?for flatten in inner,原来flatten是inner列表中的每一个元素。那么inner又是哪里来的?for inner in list,原来inner 是lst里面的每一个元素。

注意,在写的时候,这些for是按着一种从外到内的顺序写的,即先有 lst生inner(for inner in lst),再有inner生flatten ( for flatten in inner) 

所以现在清晰多了,inner对应是的lst里面的每一个元素即[1,3,5]、[2,4],而新列表中的元素是这些inner列表中的每一个元素 

注意一下:

newLst=[e1+e2 for e1 in lst1 for e2 in lst2]
print newLst
#['Aa', 'Ab', 'Ac', 'Ad', 'Ba', 'Bb', 'Bc', 'Bd', .....]

再回过来看上面刚讲过的代码,e1,e2本身不是嵌套的,这两个for是按双重for循环的顺序(即从外到内)。

注明:对于这个flatten的例子,其实可以用itertools.chain()来解决

import itertools.chain
l = [[1, 2], [3, 4]]
list(chain(*l))
#[1, 2, 3, 4]



Demo6

再写一个场景

lst=[1,2,3,4]
我想把这个lst变成这个样子

[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
应该怎么写?

有人提出一种方法,挺巧妙的,这里我们先忽略比较排序的时间复杂度问题。

lst=[1,2,3,4]
list.sort(lst*3)
print lst
#[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]

如果列表中存的元素无法比较,上面的代码就不能很好发挥他的作用了,需要重写比较函数等等 ……或者不可比较怎么办?

插一句,就像之前有面试题在考察Python的时候会问,怎么把一个list中的重复元素除掉,生成新的list,第一个想法,我想大家都会想到用set,因为集合的特点,就是不允许重复,然后再把set转成list,但是其实这里面是存在问题的,就是如果list中的元素不可hash,则无法转成相应的set。像这样[ [1,2,3 ],[4,5],[1,2,3] ] 就不行了,

TypeError: unhashable type: 'list'

看list是不能hashable的类型。

回到正题,还是上面的列表解析的问题,怎么生成 [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]

lst=[1,2,3,4]
n=3
newLst=[flatten for inner in [ [m]*n for m in lst ]  for flatten in inner  ]
print newLst
#[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]

下面是“跑分”的结果:

lst=range(1,1000)
def getLstCo(lst):
    return [flatten for inner in [ [m]*500 for m in lst ]  for flatten in inner  ]
def getLstUsingQsort(lst):
    return list.sort(lst*500)

print "list comprehension",get_execution_time(getLstCo,lst,numberOfExecTime=100)
#list comprehension 2.37426
print "using sort",get_execution_time(getLstUsingQsort,lst,numberOfExecTime=100)
#using sort 7.43793

我想,通过上面的认真学习和理解,大家应该都能读懂这段list comprehension 吧,就不再解释了。


Demo6.1

上面的问题中留下了一个疑问,即如何将[[1,2,3],[3],[1,2,3],[1,2],[1,2]]这样的列表去重,

实现的方法如下,利用itertools模块

lst=[[1,2,3],[3],[1,2,3],[1,2],[1,2]]
import itertools
lst=sorted(lst)
newLst=list(lst for lst,_ in itertools.groupby(lst))
print newLst
#[[1, 2], [1, 2, 3], [3]]


给个groupby的demo,方便理解


from itertools import groupby

things = [("animal", "bear"), ("animal", "duck"), ("plant", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print "A %s is a %s." % (thing[1], key)
    print " "


得到如下的结果


A bear is a animal.
A duck is a animal.

A cactus is a plant.

A speed boat is a vehicle. 
A school bus is a vehicle.




Demo7

对于一个给定的列表lst,[1,2,3,4.....10] ,用此生成新的列表,要求其中元素满足原列表lst中的偶数不变,其中的奇数变为相反数:

[e if not e%2 else -e for e in lst]
#[-1 2 -3 4 -5 6 -7 8 -9 10]

Demo8
lst=[1,2,3,1,2,3,4,5,3,2,1,1,5]
给定这样一个数组,如果我想让数组中的前i个元素均重新赋值为-1,
得到如下的新列表:
[-1, -1, -1, -1, -1, 3, 4, 5, 3, 2, 1, 1, 5]
lst = [1, 2, 3, 1, 2, 3, 4, 5, 3, 2, 1, 1, 5]
i = 5
newLst = [e if counter >= i else -1 for (counter, e) in enumerate(lst)]
print newLst
这里面用到了enumerate()函数,来使得我们可以记录当前迭代元素的index信息。


Demo9
去除数组中连续的重复数字,重复部分保留一个,

[9, 8, 8, 4, 4, 4, 4, 4, 3, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 8, 0]

变成如下形式:

[9, 8, 4, 3, 8, 7, 8, 0]

完整的列表解析如下:

[v for (i,v) in enumerate(lst) if  ( i>0 and lst[i]!=lst[i-1] ) or i==0 ]


加上“短路”,则可以写得更加简单:

[v for (i,v) in enumerate(lst) if not i or lst[i]!=lst[i-1] ]


你可能感兴趣的:(编程语言之Python)