第一章 数据结构和算法(10-15)

1.10 删除序列相同元素并保持顺序

问题:怎样在一个序列上保持元素顺序的同时消除重复元素
解决方案
如果序列上的值都是hashable类型,则可很简单的利用几何或者生成器进行解决。
(1)生成器: 生成器函数编写为常规的def语句,但是使用yield语句一次返回一个结果,在每个结果之间挂起和继续它们的动态。生成器表达式返回按需产生结果的一个对象,而不是构建一个结果列表。由于生成器函数和生成器表达式都不会一次性构建一个列表,它们节省内存空间,并且允许计算时间分散到各个结果请求。
(2)生成器函数: 可以送回一个值并随后从其退出的地方继续的函数。创建时,自动实现迭代协议。yield语句挂起该函数并向调用者发送回一个值,但是保留足够的状态以使得函数能从它离开的地方继续。当继续时,函数在上一个yield返回后立即继续执行。
下面建立一个生成器函数并使用:

def dedupe(items):
	seen=set()
	for item in items:
		if item not in seen:
			yield item
			seen.add(item)
>>> a=[1,5,2,1,9,1,5,10]
>>> dedupe(a)
<generator object dedupe at 0x000001CC805258E0>
>>> list(dedupe(a))
[1, 5, 2, 9, 10]

此方法仅仅在序列中元素为hashable时管用。若想消除元素不可哈希(如dict类型)的序列中重复元素的话,需将上述代码稍微改变,如下:

def dedupe(items,key=None):
	seen=set()
	for item in items:
		val=item if key is None else key(item)
		if val not in seen:
			yield item
			seen.add(val)

			
>>> a=[{'x':1,'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
>>> list(dedupe(a,key=lambda d:(d['x'],d['y'])))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
>>> list(dedupe(a,key=lambda d:d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]

讨论
利用集合进行数据去重的方法不能维护元素的顺序,生成的结果中的元素位置被打乱。

1.11 命名切片

问题:程序出现一大堆已经无法直视的硬编码切片下标,想清理代码
解决方案
假设现有如下字段:“#aa10#bg5.5#”。其中存有一些货物的信息,比如 “#aa” 表示商品名,“10” 表示数量,“#bg” 表示当下定价,“5.5” 表示具体价格。现要计算总价值,则可参考如下代码进行计算:

>>> a='#aa10#bg5.5#'
>>> num=slice(3,5)
>>> price=slice(8,11)
>>> cost=eval(a[num])*eval(a[price])
>>> cost
55.0

如此做法避免了大量无法理解的硬编码下标。
讨论
代码中如果出现大量硬编码下标,会使得可读性和可维护性大大降低。内置的slice()函数创建了一个切片对象,可以被用在任何切片允许的地方。若有一个切片对象 a,则可分别调用它的 a.starta.stopa.step属性来获取切片的起点,终点和步长。
另外,还能通过调用切片的
indices(size)方法将其映射到一个确定大小的序列上,此方法返回一个三元组(start,stop,step)
,所有值都会被合适的缩小以满足边界限制,从而使用的时候避免出现IndexError异常。例如:

>>> s='HelloWorld'
>>> a.indices(len(s))
(0, 0, 1)
>>> a=slice(5,50,2)
>>> a.indices(len(s))
(5, 10, 2)
>>> for i in range(*a.indices(len(s))):
	print(s[i])

	
W
r
d

1.12 序列中出现次数最多的元素

问题:怎样找出一个序列中出现次数最多的元素?
解决方案
collections.Counter类就是专门为这类问题而设计的,它甚至有一个有用的**most_common()**方法直接给出答案。
作为示例,先假设有一个单词列表并且想找出哪个单词出现频率最高,可以这样做:

>>> words=['look','into','my','eyes','look','into','the','eyes','not','the','your']
>>> from collections import Counter
>>> word_counts=Counter(words)
>>> top_three=word_counts.most_common(3)
>>> top_three
[('look', 2), ('into', 2), ('eyes', 2)]

讨论
作为输入,Counter对象可以接受任意的hashable序列对象。在底层实现上,一个Counter对象就是一个字典,将元素映射到它出现的次数上,例如:

>>> word_counts['not']
1
>>> word_counts['the']
2

若想手动增加计数,可以简单的用加法,或者使用**update()**方法。同时,Counter实例一个鲜为人知的特性是它们可以很容易与数学运算操作相结合。

1.13 通过某个关键字排序一个字典列表

问题:有一个字典列表,想根据某个或某几个字典字段来排序这个列表
解决方案
通过使用operator模块itemgetter()函数,可以非常容易的排序这样的数据结构。
operator模块输出一系列对应Python内部操作符的函数。例如:operator.add(x, y)等价于表达式x+y。许多函数的名称都被一些特定的方法使用,没有下划线加持。为了向下兼容,它们中的许多都保留着由双下划线的变体。那些不具备双下划线的变体是为了使表达更清晰。
这些函数在各种函数目录里扮演者对相比较、逻辑操作、数学运算以及序列操作等角色。
对于所有对象来讲对象比较函数是十分有用的,并且这些函数以它们支持的丰富的比较操作命名。
例如:

>>> rows=[{'fname':'Brian','lname':'Jones','uid':1003},
      {'fname':'David','lname':'Beazley','uid':1002},
      {'fname':'John','lname':'Cleese','uid':1001},
      {'fname':'Big','lname':'Jones','uid':1004}]
>>> from operator import itemgetter
>>> rows_by_fname=sorted(rows,key=itemgetter('fname'))
>>> rows_by_uid=sorted(rows,key=itemgetter('uid'))
>>> rows_by_fname
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
>>> rows_by_uid
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]

itemgetter() 函数也支持多个keys
讨论

上例中,rows被传递给接受一个关键字参数的sorted()内置函数。这个参数是callable类型

1.14 排序不支持原生比较的对象

问题:你想排序类型相同的对象,但是他们不支持原生的比较操作。
解决方案
内置的sorted()函数有一个关键字参数key,可以传入一个callable对象给它,这个callable对象对每个传入的对象返回一个值,这个值会被sorted用来排序这些对象。
例如,如果在程序汇总有一个User实例,并且希望通过他们的user_id属性进行排序,可以提供一个以User实例作为输入并输出对应user_id值的callable对象。如:

class User:
    def __init__(self,user_id):
        self.user_id=user_id

    def __repr__(self):
        return 'User({})'.format(self.user_id)

def sort_notcompare():
    users=[User(23),User(3),User(99)]
    print(users)
    print(sorted(users,key=lambda u:u.user_id))

sort_notcompare()

结果如下:

[User(23), User(3), User(99)]
[User(3), User(23), User(99)]

另一种方式是使用operator.attrgetter()来代替lambda函数:

>>> users=[User(23),User(3),User(99)]
>>> from operator import attrgetter
>>> sorted(users,key=attrgetter('user_id'))
[User(3), User(23), User(99)]

讨论
**attrgetter()**函数通常会运行的快点,并且还能同时允许多个字段进行比较。

1.15 通过某个字段将记录分组

问题:你有一个字典或者实例的序列,然后你想根据某个特定的字段比如data来分组迭代访问。
解决方案
itertools.groupby() 函数对于这样的数据分组操作非常实用。
假设已有如下字典列表:

rows=[
    {'address':'5412 N CLARK','date':'07/01/2012'},
    {'address':'5148 N CLARK', 'date':'07/04/2012'},
    {'address':'5800 E 58TH','date':'07/02/2012'},
    {'address':'2122 N CLARK','date':'07/03/2012'},
    {'address':'5645 N RAVENSWOOD','date':'07/02/2012'},
    {'address':'1060 W ADDISON','date':'07/02/2012'},
    {'address':'4801 N BROADWAY','date':'07/01/2012'},
    {'address':'1039 W GRANVILLE','date':'07/04/2012'}
]

现在想在按date分组后的数据块上进行迭代。首先需按照指定的字段(date)排序,然后调用itertools.groupby() 函数:

from operator import itemgetter
from itertools import groupby

rows.sort(key=itemgetter('date'))
for date,items in groupby(rows,key=itemgetter('date')):
    print(date)
    for i in items:
        print('  ',i)

运行结果如下:

07/01/2012
   {'address': '5412 N CLARK', 'date': '07/01/2012'}
   {'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
   {'address': '5800 E 58TH', 'date': '07/02/2012'}
   {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
   {'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
   {'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
   {'address': '5148 N CLARK', 'date': '07/04/2012'}
   {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}

讨论

**groupby()**函数扫描整个序列并查找连续相同值(或者根据指定key函数返回值相同)的元素序列。在每次迭代时,会返回一个值和一个迭代器对象,这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。
一个非常重要的准备步骤是 要根据指定的字段将数据排序。 因为 groupby() 仅仅检查连续的元素。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值