Fork me on GitHub

《流畅的Python》一副扑克牌中的难点

1.现在在看《流畅的Python》这本书,看了三页就发现,这本书果然不是让新手来入门的,一些很常见的知识点能被这个作者玩出花来,

唉,我就在想,下面要分析的这些的代码,就算我费劲巴拉的看懂了,又有什么用呢,我其实不想靠着技术吃饭,但是现在在这个岗位上,

就得在其位谋其职,悲哀。我在敲代码方面也没什么天赋,也没什么热情,老话说得好,‘女怕嫁错郎,男怕入错行’,

所以我得从IT行业抽出身来。anyway,进入正题。

2.python解释器碰到特殊的句法时,会使用特殊方法去激活一些基本的对象操作,这些特殊方法的名字以两个下划线开头,

以两个下划线结尾(例如__getitem__)。比如obj[key]的背后就是__getitem__方法,为了求得my_collection[key]的值,解释器实际上会调用my_collection.__getitem__(key),双下划线这种特殊方法也叫双下方法(dunder method).

import collections

Card = collections.namedtuple('Card',['rank','suit'])


class FrenchDeck:
    ranks = [str(n) for n in range(2,11)] + list('JQKA')
    # ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank,suit) for suit in self.suits for rank in self.ranks]

    def __len__(self):
        # print("用的是我写的lenth")
        # 如果不写这个方法,用len(deck)时会报错:TypeError: object of type 'FrenchDeck' has no len()
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

 

>>> beer_card = Card('7','hearts')
>>> beer_card
Card(rank='7', suit='hearts')
这张牌就是红心7

 

FrenchDeck类中的self._cards生成了一个有52张'牌'的列表
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), ...共52个]
suits = ['spades', 'diamonds', 'clubs', 'hearts']
namedtuple的作用:
>>> beer_card = Card('7','hearts')
>>> beer_card
Card(rank='7', suit='hearts')
FrenchDeck类支持的方法:
>>> deck = FrenchDeck()
>>> len(deck)
52
>>> from random import choice
>>> choice(deck)
支持分片:只看牌面是A的牌--先抽出索引是12的那张牌,然后每隔13张拿一张
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'),
 Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
这个我觉得作者的用法很好
仅仅用__getitem__方法,使这一摞牌变成可迭代
>>> for card in deck:
...     print(card)
反向迭代
>>> for card in reversed(deck):
...     print(card)
一个集合类型如果没有实现__contains__方法,in运算符就会按顺序做一次迭代搜索
>>> Card(rank='A', suit='spades') in deck
True

下面这段就厉害了,弄得我眼黑头晕的,弄了一早上才搞明白,怎么对这些扑克牌进行排序?

首先你会想到sorted,但我连它的key参数都不会用,何谈去排序这些扑克。sorted用法:

sorted最简单用法是对元素全部是数字的列表排序,另一种用法是对元素是元组或字典的列表排序

用到的参数是key,比如:对学生的分数进行排序:

list1 = [('david', 90), ('mary',90), ('sara',80),('lily',95)]
sorted(list1, key=lambda x: x[0])  # 按照元组中第一个元素排序
sorted(list1, key=lambda x: x[1])  # 按照元组中第二个元素排序
array = [{"age":20,"name":"a"},{"age":25,"name":"b"},{"age":10,"name":"c"}]
array1 = sorted(array,key=lambda x:x["age"])  # 按照年龄排序

 下面要讲的例子,先抛个砖:

1.需求:将列表中的元素按照绝对值大小进行升序排列
list1 = [3,5,-4,-1,0,-2,-6]
sorted(list1, key=lambda x: abs(x))

当然,也可以如下:

list1 = [3,5,-4,-1,0,-2,-6]
def get_abs(x):
    return abs(x)
sorted(list1,key=get_abs)
只不过这种方式的代码看起来不够Pythonic
2、应用在闭包中
def get_y(a,b):
     return lambda x:ax+b
y1 = get_y(1,1)
y1(1) # 结果为2

当然,也可以用常规函数实现闭包,如下:

def get_y(a,b):
    def func(x):
        return ax+b
    return func
y1 = get_y(1,1)
y1(1) # 结果为2
只不过这种方式显得有点啰嗦。
那么是不是任何情况下lambda函数都要比常规函数更清晰明了呢?
Python之禅中有这么一句话:Explicit is better than implicit(明了胜于晦涩),就是说那种方式更清晰就用哪一种方式,不要盲目的都使用lambda表达式。

 扑克牌排序代码:

suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)
# {'clubs': 0, 'diamonds': 1, 'spades': 3, 'hearts': 2}

def spades_high(card):
    rank_values = FrenchDeck.ranks.index(card.rank)
    return rank_values * len(suit_values) + suit_values[card.suit]

for card in sorted(deck,key=spades_high):
    print(card)

一、这段代码我看了很长时间,这本书的作者竟然将这段代码放在书的第二页,我也是醉了,我觉得是挺难的,但不管怎样,硬着头皮上吧!

扑克牌排序规则:

用点数来判断扑克牌的大小,2最小,A最大;同时还要加上对花色的判断:

黑桃最大,红桃次之,方块再次,梅花最小。那么梅花2的大小是0,黑桃A是51.

分析一下这个函数:sorted(deck,key=spades_high),我在这里卡了很长时间,所以就有了上面的那块砖,先说一下这个函数是怎么运行的吧:

通过sorted将deck这一摞牌和排序函数spades_high()联系起来,每张牌都有数字,先获取牌面的数字在FrenchDeck.ranks中的索引,即:2的索引0,

3的索引1,4的索引2,类推3,4,5,6,7,8,9,10,11,A的索引12,这张牌多大取决于它的花色,即索引 乘 4 加 花色值,

二、spades_high(card),这里的card就是传进来的每一张牌,rank_values = FrenchDeck.ranks.index(card.rank),

获取这张纸牌(即纸牌的数字)在FrenchDeck.ranks中的索引,['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']

"最后返回这个牌的得分",rank_values * len(suit_values) + suit_values[card.suit],此时就讲完了么?

灵光一闪,这个函数返回了所有得分有什么用?sorted()拿到这些得分,怎么把牌排好序?我觉得是这样:

在得到了[所有得分]这个列表之后,进行排序,得到:[0,1,2,3,4,...51],

然后将原扑克列表中的元素,一一对应,是怎么对应的?函数在处理的时候应该记录了每张卡的得分,所以能对应上。

如下(原来的扑克列表可不是这样的,排好序的应该是重新开辟了一个内存空间):

[Card(rank='2', suit='clubs'),Card(rank='2', suit='diamonds'),Card(rank='2', suit='hearts'),...Card(rank='A', suit='spades')]

 三、比如上面第一块砖:

list1 = [3,5,-4,-1,0,-2,-6]

def get_abs(x):
    return abs(x)
sorted(list1,key=get_abs)

函数先返回[3,5,4,1,0,2,6],然后排序[0,1,2,3,4,5,6],接着将list1中的每个元素,与排序后的列表对应,

0不变,1、2变成-1、-2,3不变,4变成-4,5不变,6变成-6,输出[0,-1,-2,3,-4,5,-6]

 四、看不懂的lambda,只是把这两道题放在在这里提醒我,不要迷进去:

一道面试题:
list1=[7, -8, 5, 4, 0, -2, -5]
要求1.正数在前负数在后 2.整数从小到大 3.负数从大到小
解题思路:先按照正负排先后,再按照大小排先后
list1=[7, -8, 5, 4, 0, -2, -5]
print(sorted(list1,key=lambda x:(x<0,abs(x))))
[7, 5, 4, 0, -8, -2, -5]
[0, 4, 5, 7, -2, -5, -8]

一个经典的复杂例子 
这是一个字符串排序,排序规则:小写<大写<奇数<偶数 
s = ‘asdf234GDSdsf23’  #排序:小写-大写-奇数-偶数 
print(“”.join(sorted(s, key=lambda x: (x.isdigit(),x.isdigit() and int(x) % 2 == 0,x.isupper(),x)))) 
原理:先比较元组的第一个值,FALSE

   也不说什么脏话了,反正没什么成就感,这些东西耗费我整整一天时间啊,就这本书作者随手扔出来的一些小玩意,搞得我很不堪,

这就是传说当中的以我之短击他之长吧,法克!但还是会有不服,别人能会,我为什么嫌难,人总是活在矛盾中。总之,以后尽量不搞这些东西了。

posted @ 2018-10-10 18:04  法外狂徒  阅读(3091)  评论(0编辑  收藏  举报