鱼C论坛

 找回密码
 立即注册
楼主: lightninng

[技术交流] [记录贴]每天的问题和解决方法_正确学习方法

[复制链接]
 楼主| 发表于 2015-4-30 15:57:10 | 显示全部楼层
~风介~ 发表于 2015-4-30 14:57
之前也纠结过这个问题, 后来决定一律用小峰驼法。:)

恩,主要是积重难返,之前做那个的时候都是简单的小模块程序,我就全改成PEP 8的写法,这次最后一章是一个完整小游戏,作者的程序中全部用的都是小驼峰,让我有些犹豫~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-4-30 17:15:13 | 显示全部楼层
lightninng 发表于 2015-4-30 15:57
恩,主要是积重难返,之前做那个的时候都是简单的小模块程序,我就全改成PEP 8的写法,这次最后一章是一 ...

这种需要慢慢来——就像忘记一个人...
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-6 00:17:43 | 显示全部楼层
本帖最后由 lightninng 于 2015-5-6 00:19 编辑

上一次更这个贴子已经是6天前了,最近pyqt那个贴遇到瓶颈,自己又在改论文不想去弄它(说明变懒了),五一本来想把论文写好,因为各种各样的原因反而玩了4天,今天写了一天论文,到晚上莫名其妙的电脑开始频繁死机,最后一次死机直接打不开了,吓的我一身冷汗(10号之前要把论文交上去),最后想到手机还插在上面,果然拔掉手机电脑又打开了,我在想老天是不是惩罚我这几天不干活啊,pyqt那个暂时不更,但是每天还是要学点东西,正好之前找到了python cook book第三版的英文版,以及一位大神正在施工中的中文版,准备每天看一些,有兴趣的朋友网址如下:http://python3-cookbook.readthedocs.org/zh_CN/latest/
1、任何的序列(或者是可迭代对象)可以通过一个简单的赋值语句解压并赋值给多个变量
直接引用原文的例子
  1. >>> p = (4, 5)
  2. >>> x, y = p
  3. >>> x
  4. 4
  5. >>> y
  6. 5
  7. >>>
  8. >>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
  9. >>> name, shares, price, date = data
  10. >>> name
  11. 'ACME'
  12. >>> date
  13. (2012, 12, 21)
  14. >>> name, shares, price, (year, mon, day) = data
  15. >>> name
  16. 'ACME'
  17. >>> year
  18. 2012
  19. >>> mon
  20. 12
  21. >>> day
  22. 21
  23. >>>
复制代码
PS:这个方法在python中极为常用,如你想将两个变量(假如这两个变量是x,y)的赋值进行交换时,python中可以这样写x, y = y, x;同理 这个方法可以拓展到任意个变量作任意方式的交换
有时候,你可能只想解压一部分,丢弃其他的值。对于这种情况Python并没有提供特殊的语法。 但是你可以使用任意变量名去占位,到时候丢掉这些变量就行了。
  1. >>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
  2. >>> _, shares, price, _ = data
  3. >>> shares
  4. 50
  5. >>> price
  6. 91.1
  7. >>>
复制代码
PS:说到占位符,最先想到的是C语言中字符串格式化的占位符(当然python中也有),这里的占位符其实是一个变量,python中允许使用_开头的变量,这个例子中的_其实是一个变量名,当然你可以用任何一个你不会使用的变量名作为占位符,但是_是一个比较好的选择,另外书中提到的另外一个不错的选择是 ign,我觉得这没有为什么,可能只是一种约定俗成


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-5-6 12:11:48 | 显示全部楼层
有部分还看不懂,全学会再回来看看
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-5-6 16:22:29 | 显示全部楼层
楼主棒棒哒,我是来赚取鱼币的:lol:
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-6 22:58:17 | 显示全部楼层
shenxiang839 发表于 2015-5-6 12:11
有部分还看不懂,全学会再回来看看

最近应该持续会更Python cook book当中的内容,不算难,而且体现python这门语言的特点,欢迎来交流~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-6 23:13:25 | 显示全部楼层
问题:如果一个可迭代对象的元素个数超过变量个数时,会出现”太多解压值”的异常。 那么怎样才能从这个可迭代对象中解压出N个元素出来?
解决:Python的星号表达式可以用来解决这个问题
比如,你在学习一门课程,在学期末的时候, 你想统计下家庭作业的平均成绩,但是排除掉第一个和最后一个分数。如果只有四个分数,你可能就直接去简单的手动赋值, 但如果有24个呢?这时候星号表达式就派上用场了:
  1. def drop_first_last(grades):
  2.     first, *middle, last = grades
  3.     return avg(middle)
复制代码
另外一种情况,假设你现在有一些用户的记录列表,每条记录包含一个名字、邮件,接着就是不确定数量的电话号码。 你可以像下面这样分解这些记录:
  1. >>> record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
  2. >>> name, email, *phone_numbers = user_record
  3. >>> name
  4. 'Dave'
  5. >>> email
  6. 'dave@example.com'
  7. >>> phone_numbers
  8. ['773-555-1212', '847-555-1212']
  9. >>>
复制代码
PS:这个方法也相当的常用,不过看到这个我想起的是甲鱼小哥的视频,我想看过甲鱼小哥视频中函数那几集的鱼油,应该都记得函数的参数中有一种叫作收集参数(我忘了甲鱼小哥把它叫什么了),这种参数在函数定义时用的正是*args这种写法,传入的参数正是一个列表,这里用*表达式解压得到的也是一个列表(哪怕它只有一个元素)

原文中给出的另外两个例子如下(我就全贴了,因为我觉得例子比描述更有说服力):
  1. """带有标签的元组序列"""
  2. records = [
  3.     ('foo', 1, 2),
  4.     ('bar', 'hello'),
  5.     ('foo', 3, 4),
  6. ]

  7. def do_foo(x, y):
  8.     print('foo', x, y)

  9. def do_bar(s):
  10.     print('bar', s)

  11. for tag, *args in records:
  12.     if tag == 'foo':
  13.         do_foo(*args)
  14.     elif tag == 'bar':
  15.         do_bar(*args)
复制代码
  1. """字符串分割"""
  2. >>> line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
  3. >>> uname, *fields, homedir, sh = line.split(':')
  4. >>> uname
  5. 'nobody'
  6. >>> homedir
  7. '/var/empty'
  8. >>> sh
  9. '/usr/bin/false'
  10. >>>
复制代码
  1. """和昨天一样的占位符,_取到的内容是自己不需要的"""
  2. >>> record = ('ACME', 50, 123.45, (12, 18, 2012))
  3. >>> name, *_, (*_, year) = record
  4. >>> name
  5. 'ACME'
  6. >>> year
  7. 2012
  8. >>>
复制代码

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-5-7 10:31:23 | 显示全部楼层
还没入门,看你的帖子还看不懂,但是你的学习方法给了我很好的榜样,希望以后能多多交流学习:loveliness:
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-7 12:24:24 | 显示全部楼层
小小云儿 发表于 2015-5-7 10:31
还没入门,看你的帖子还看不懂,但是你的学习方法给了我很好的榜样,希望以后能多多交流学习

欢迎交流~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-7 23:17:02 | 显示全部楼层
本帖最后由 lightninng 于 2015-5-7 23:25 编辑

在迭代操作或者其他操作的时候,怎样只保留最后有限几个元素的历史记录?
保留有限历史记录正是 collections.deque 大显身手的时候。比如,下面的代码在多行上面做简单的文本匹配, 并只返回在前N行中匹配成功的行:
  1. from collections import deque


  2. def search(lines, pattern, history=5):
  3.     previous_lines = deque(maxlen=history)
  4.     for li in lines:
  5.         if pattern in li:
  6.             yield li, previous_lines
  7.         previous_lines.append(li)

  8. # Example use on a file
  9. if __name__ == '__main__':
  10.     with open(r'../../cookbook/somefile.txt') as f:
  11.         for line, prevlines in search(f, 'python', 5):
  12.             for pline in prevlines:
  13.                 print(pline, end='')
  14.             print(line, end='')
  15.             print('-' * 20)
复制代码
PS:上面的例子,中我自己学习到的地方:

1、通过yield语句返回的函数,生成的函数叫作生成器函数,它返回的是一个可迭代对象(最典型的例子就是我们经常会用到的range()),不懂yield语句的朋友请移步http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00138681965108490cb4c13182e472f8d87830f13be6e88000

2、函数可以传入一个可迭代对象或者生成器(其实这个早已经知道了,之前学到的itertools.product()),在这个例子中传入search()函数的第一个参数是一个文件对象,它通过迭代不停的寻找匹配项加入previous_lines中
3、collections模块的deque(即双端队列)在戴宇轩小哥的http://bbs.fishc.com/forum.php?mod=viewthread&tid=59409&ctid=157这个贴子中已经介绍过,当时没有太多的想法,看到这个例子想起了一些东西:
(1)deque这种数据对象在队列头部插入时时间复杂度都为O(1),和list对象的O(n)相比效率提高太多,
(2)这个例子中给我们演示了deque的参数maxlen
(3)deque这个数据结构可以方便的使用appendleft和append方法在两端插入元素,当元素总数超过maxlen时,会从另一端删除元素,正是这个特性使我们可以保留最近n个数据
还是原书关于deque的例子,我把它稍作修改,使得最后一次插入元素从左侧插入,因为元素总数超过3,所以会从右侧删除一个元素
  1. >>> q = deque(maxlen=3)
  2. >>> q.append(1)
  3. >>> q.append(2)
  4. >>> q.append(3)
  5. >>> q
  6. deque([1, 2, 3], maxlen=3)
  7. >>> q.append(4)
  8. >>> q
  9. deque([2, 3, 4], maxlen=3)
  10. >>> q.appendleft(5)
  11. >>> q
  12. deque([5, 2, 3], maxlen=3)
复制代码






想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-9 00:57:25 | 显示全部楼层
问题:怎样从一个集合中获得最大或者最小的N个元素列表?
解决:heapq模块有两个函数:nlargest()nsmallest() 可以完美解决这个问题。
  1. import heapq
  2. nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
  3. print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
  4. print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]
复制代码
两个函数都能接受一个关键字参数,用于更复杂的数据结构中:
  1. portfolio = [
  2.     {'name': 'IBM', 'shares': 100, 'price': 91.1},
  3.     {'name': 'AAPL', 'shares': 50, 'price': 543.22},
  4.     {'name': 'FB', 'shares': 200, 'price': 21.09},
  5.     {'name': 'HPQ', 'shares': 35, 'price': 31.75},
  6.     {'name': 'YHOO', 'shares': 45, 'price': 16.35},
  7.     {'name': 'ACME', 'shares': 75, 'price': 115.65}
  8. ]
  9. cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
  10. expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
复制代码
PS:这个参数key,用max和min两个函数用的多的同学相信比较熟悉了,不懂的,先去看看max和min两个函数的key参数,举一反三,这里的key参数是同样 的用法
根据原书中的说法,这两个函数实际上是对列表中的元素进行了堆排序,堆是一种数据结构, 基本上只要是数据结构和算法书籍里面都会有提及到,不知道甲鱼小哥的数据结构视频中有否提到,但它确实一种很有用的数据结构,由其是这个问题中所遇到的情况。
另外,你可以用heapq.heapify()函数将一个列表转化为堆,转化为堆后,列表的第一个元素是最小的元素
  1. >>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
  2. >>> import heapq
  3. >>> heapq.heapify(nums)
  4. >>> nums
  5. [-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8]
  6. >>>
复制代码
另外 ,你可以用
heapq.heappop()函数弹出最小的元素,并将剩下的元素依然保持为堆结构,换句话说,该函数会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是O(N),N是堆大小,这里的时间复杂度指的是最坏情况下的时间复杂度)。这样我们想找最小的三个元素只需要调用三次heapq.heappop()函数即可
最后,书的作者提醒我们nlargest() 和 nsmallest()这两个函数在你需要找的元素远小于总元素个数时,效率不错,但当你要找的元素个数接近总元素个数时,用这两个函数不如你直接排序加列表切片来的实在,正如作者所说在正确的场合使用正确的工具才能让你的工作进行的更有效率。





想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-5-9 09:11:29 | 显示全部楼层
lightninng 发表于 2015-4-3 10:30
谢谢,学习中,多交流感觉学的更快~~
欢迎吐槽~~

{:1_1:}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-10 00:55:43 | 显示全部楼层
问题:怎样实现一个按优先级排序的队列? 并且在这个队列上面每次pop操作总是返回优先级最高的那个元素
解决:下面的类利用heapq模块实现了一个简单的优先级队列,该队列调用pop()方法可弹出优先级最高的元素,如果优先级最高的元素有多个,则按插入顺序返回最先插入的元素
  1. import heapq

  2. class PriorityQueue:
  3.     def __init__(self):
  4.         self._queue = []
  5.         self._index = 0

  6.     def push(self, item, priority):
  7.         heapq.heappush(self._queue, (-priority, self._index, item))
  8.         self._index += 1

  9.     def pop(self):
  10.         return heapq.heappop(self._queue)[-1]
复制代码
在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。
index变量的作用是保证同等优先级元素的正确排序,也就是说index表明这个元素是第几个插入的元素。 通过保存一个不断增加的index下标变量,可以确保元素安装它们插入的顺序排序。 而且,index变量也在相同优先级元素比较的时候起到重要作用。
PS:昨天的那个小节,我们已经接触到了heapq这个模块,知道它相关的函数其实是把列表按堆的方式排序,这里的heappop()函数昨天已经介绍过,heappush与其相反, 是给列表插入一个元素,然后再重新把插入后的列表建成堆,看到这里我有点不能理解,因为这里列表的元素是元组,怎么排序呢?看了看说明,感觉可能是按每个元组的第一个元素排序,如果两个元组第一个元素相同 ,则按第二个元素确定它们的次序,以此类推下去,用max函数验证如下:
  1. >>> max_test = list(zip(range(10), range(-10,1)))
  2. >>> max_test
  3. [(0, -10), (1, -9), (2, -8), (3, -7), (4, -6), (5, -5), (6, -4), (7, -3), (8, -2), (9, -1)]
  4. >>> max(max_test)
  5. (9, -1)
  6. >>> max_test.append((9, 1))
  7. >>> max_test.append((9, -2))
  8. >>> max_test
  9. [(0, -10), (1, -9), (2, -8), (3, -7), (4, -6), (5, -5), (6, -4), (7, -3), (8, -2), (9, -1), (9, 1), (9, -2)]
  10. >>> max(max_test)
  11. (9, 1)
  12. >>>
复制代码




想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-11 01:02:26 | 显示全部楼层
问题:怎样实现一个键对应多个值的字典(也叫 multidict )?
解决:使用collections模块中的defaultdict来构造值(value)为列表、集合或字典的字典
  1. from collections import defaultdict

  2. d = defaultdict(list)
  3. d['a'].append(1)
  4. d['a'].append(2)
  5. d['b'].append(4)
  6. print(d)
  7. d = defaultdict(set)
  8. d['a'].add(1)
  9. d['a'].add(2)
  10. d['b'].add(4)
  11. print(d)
  12. d['a'].update({1:"a"})
  13. d['a'].update({2:"b"})
  14. d['a'].update({3:"c"})
  15. print(d)
复制代码
这段代码的输出如下

  1. >>> ================================ RESTART ================================
  2. >>>
  3. defaultdict(<class 'list'>, {'a': [1, 2], 'b': [4]})
  4. defaultdict(<class 'set'>, {'a': {1, 2}, 'b': {4}})
  5. defaultdict(<class 'dict'>, {'a': {1: 'a', 2: 'b', 3: 'c'}})
  6. >>>
复制代码
PS:相信看到戴小哥的这个贴子的鱼友http://bbs.fishc.com/forum.php?mod=viewthread&tid=59396&ctid=157,会想起字典的这个方法setdefault(k[, d]),我自己在做论文时遇到这个问题正是用的它比如这样
  1. d = {} # A regular dictionary
  2. d.setdefault('a', []).append(1)
  3. d.setdefault('a', []).append(2)
  4. d.setdefault('b', []).append(4)
  5. print(d)
复制代码
这段代码的输出是这样的
  1. >>> ================================ RESTART ================================
  2. >>>
  3. {'b': [4], 'a': [1, 2]}
  4. >>>
复制代码
setdefault()用起来有点别扭。因为每次调用都得创建一个新的初始值的实例(例子程序中的空列表[])。
  1. d = {}
  2. for key, value in pairs:
  3.     if key not in d:
  4.         d[key] = []
  5.     d[key].append(value)
复制代码
先检查键是否在字典中,如果不存在则先添加键值如果使用defaultdict的话就简单多了
  1. d = defaultdict(list)
  2. for key, value in pairs:
  3.     d[key].append(value)
复制代码
相信只要作数据处理,都会遇到这个问题,这个技巧可以说是相当 的实用



想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-11 23:36:12 | 显示全部楼层
我们都知道字典中键值排列的顺序是和插入顺序无关的,也就是说在根据键值顺序进行迭代时,迭代的顺序和插入顺序可能会不同(这个可能性相当的大),那么问题来了
问题:想创建一个字典,并且在迭代或序列化这个字典的时候能够控制元素的顺序。
解决:使用collections模块中的OrderedDict类。
  1. from collections import OrderedDict

  2. d = OrderedDict()
  3. d['foo'] = 1
  4. d['bar'] = 2
  5. d['spam'] = 3
  6. d['grok'] = 4
  7. for key in d:
  8.     print(key, d[key])
复制代码
这个例子的输出如下

  1. >>> ================================ RESTART ================================
  2. >>>
  3. foo 1
  4. bar 2
  5. spam 3
  6. grok 4
  7. >>>
复制代码
另外,如果你想精确控制以JSON编码后字段的顺序,你可以先使用OrderedDict来构建这样的数据d,然后用下面的方法:
  1. >>> import json
  2. >>> json.dumps(d)
  3. '{"foo": 1, "bar": 2, "spam": 3, "grok": 4}'
  4. >>>
复制代码
PS:以上是用法,作者还提醒我们,orderdict类是用一个链表结构来维护键的插入顺序的,所以字典的大小将是普通字典的两倍,所以当你创建一个很大的字典的时候,需要考虑一下内存占用的问题,这是一个相当实际的问题,我用普通的字典处理一个有1w个键值的字典,字典的值均为长度为10的列表,我的8G内存就已经快抗不住了~~


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-13 01:16:50 | 显示全部楼层
今天的问题也是一个实际的问题
问题:怎样在数据字典中执行一些计算操作(比如求最小值、最大值、排序等等)?
解决:使用zip()函数先将键和值反转过来,具体见下面的例子

  1. prices = {
  2.     'ACME': 45.23,
  3.     'AAPL': 612.78,
  4.     'IBM': 205.55,
  5.     'HPQ': 37.20,
  6.     'FB': 10.75
  7. }
  8. min_price = min(zip(prices.values(), prices.keys()))
  9. print(min_price)
  10. max_price = max(zip(prices.values(), prices.keys()))
  11. print(max_price)
复制代码
这段代码的输出为

  1. >>> ================================ RESTART ================================
  2. >>>
  3. (10.75, 'FB')
  4. (612.78, 'AAPL')
  5. >>>
复制代码
可以看到,这里作的是对键值的排序操作,返回值是(值,键)的元组,有些同学可能会想到运用min或者max的key参数达到同样的效果,比如下面这样

  1. >>> min(prices, key=lambda k: prices[k])
  2. 'FB'
  3. >>> max(prices, key=lambda k: prices[k])
  4. 'AAPL'
复制代码
得到了值为最大和最小的键,但是如果你想获得最大值和最小值,还得再做一次字典的取值操作

可以看到,两者的效率应该是差不多的,但是通过元组的方法在使用的时候更加的灵活,这里又用到了之前提到的元组的比较,即以元组的第一个元素的大小关系为两个元素的大小关系,当第一个元素相同时,才会用到第二个元素,以此类推广,所以当两个键的值都最为最大(或最小)时 ,会返回键值更大(或小)的那一对(值,键)元组
另外 ,肯定有鱼油发现了当你对字典进行求最大(或最小)值时,实际是求键的最大(或最小)值,同样你直接对字典进行迭代时,实际是对键进行迭代即下面两组语句效果是相同的:
  1. for each in prices:
  2.     print(each)
  3. #--------------------------------------------------
  4. for each in prices.keys():
  5.     print(each)
复制代码
最后需要注意的一个问题是:
执行这些计算的时候,需要注意的是zip()函数创建的是一个只能访问一次的迭代器。来看一个例子:
  1. prices_and_names = zip(prices.values(), prices.keys())
  2. print(min(prices_and_names)) # OK
  3. print(max(prices_and_names)) # ValueError: max() arg is an empty sequence
复制代码
它的结果为:

  1. >>> ================================ RESTART ================================
  2. >>>
  3. (10.75, 'FB')
  4. Traceback (most recent call last):
  5.   File "C:\Users\Administrator\Desktop\11.py", line 10, in <module>
  6.     print(max(prices_and_names)) # ValueError: max() arg is an empty sequence
  7. ValueError: max() arg is an empty sequence
  8. >>>
复制代码
当然迭代器的每一项是在迭代过程中生成的,所以并不占用内存,所以一次或者两次都无所谓,但是如果多次使用最大或者最小值,或者你想得到第三大,或者第四大的值对应的(值,键)对时,最好还是专门创建一个列表来存放它们,比如这样:

  1. prices_sorted = sorted(zip(prices.values(), prices.keys()))
复制代码
PS:要说的话,这个问题遇到的还真的还是满多的,楼主悔恨中,当初要是看了这本书再写论文要省好多事的~~







想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-15 00:49:33 | 显示全部楼层
问题:怎样在两个字典中寻寻找相同点(比如相同的键、相同的值等等)?
解决:用集合操作
  1. a = {
  2.     'x' : 1,
  3.     'y' : 2,
  4.     'z' : 3
  5. }

  6. b = {
  7.     'w' : 10,
  8.     'x' : 11,
  9.     'y' : 2
  10. }
复制代码

对于上面两个字典,我们怎么求得它们相同的键(或者键值对)不同的键呢?
  1. >>> a.keys() & b.keys()
  2. {'x', 'y'}
  3. >>> a.items() & b.items()
  4. {('y', 2)}
  5. >>> a.keys() - b.keys()
  6. {'z'}
复制代码

PS:以及相信大家在平时的编程过程中用到最多的容器是list和dict两种了吧,对于集合set有些同学可能比较陌生,不过甲鱼小哥的视频中是有讲过的,集合有几个特点:
1、集合中元素不可变(所以集合的元素不能是列表),且不能重复,set中元素的排序和插入顺序无关,这一点和dict中的key的属性有点像,所以集合用的也是大括号,可以说是只有键没有值的字典
2、集合支持一些特有的操作,求交集&,并集|, 以及求差集-,请看例子
  1. >>> a = {1, 2, 3}
  2. >>> b = {1, 2, 4}
  3. >>> a & b
  4. {1, 2}
  5. >>> a | b
  6. {1, 2, 3, 4}
  7. >>> a - b
  8. {3}
  9. >>>
复制代码

这个问题的解决方法告诉我们字典的keys(), items()方法得到的可迭代对象(实际上它是一种视图)也支持集合的相关操作,这为我们解决问题提供了方便,比如 你想以现有字典构造一个排除几个指定键的新字典。你可以这么做
  1. >>> c = {key:a[key] for key in a.keys() - {'z', 'w'}}
  2. >>> c
  3. {'x': 1, 'y': 2}
复制代码
到这里有些鱼油会提出疑问,为什么values()方法不可以?答案是,字典的value值并不是唯一的,这一点不符合集合的特性,当然如果你想求两个字典相同的值有哪些,你可以将values()方法返回的可迭代对象用set()方法强行转化为集合再用&操作求出相同的值
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-16 02:20:25 | 显示全部楼层
花了两个多小时解决了pyqt那个俄罗斯方块代码中的bug,事实证明我还是太马虎了,三个错误都是由自己的输入失误造成的,下一步要实现界面和业务的逻辑分离,悲惨的发现Mr.R大神的那个pyqt5入门教程的网站上不去了,http://hi.baidu.com/lovebabycase,网上有好多转载的但是图都没有截好,还说从上面学一学界面和业务分离的做法,只能自己研究了,先睡觉了,困死~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-18 00:05:29 | 显示全部楼层
本帖最后由 lightninng 于 2015-5-21 23:05 编辑

好久没玩checkio了,今天来玩一下,今天的问题是这个
Xs and Os Referee
井字游戏,有时也被称为“进攻和防守”,是一个两人玩家(X和O)轮流标志着3×3的网格的空间的连珠游戏。最先在任意一条直线(水平线,垂直线或对角线)上成功连接三个标记的一方获胜。
但我们不去玩这个游戏。你将是这个游戏的裁判。你被赋予游戏的结果,以及你必须判断游戏是平局还是有人胜出,以及谁将会成为最后的赢家。如果X玩家获胜,返回“X”。如果O玩家获胜,返回“O”。如果比赛是平局,返回“D”。

                               
登录/注册后可看大图
游戏的结果是作为字符串形式的列表,其中“X”和“O”是玩家的标志,“.”是空格。
我第一次写出来的程序
  1. def checkio(game_result):
  2.     board = "".join(game_result)
  3.     win_lines = [slice(0, 3), slice(3, 6), slice(6, 9),
  4.                  slice(0, 9, 3), slice(1, 9, 3), slice(2, 9, 3),
  5.                  slice(0, 9, 4), slice(2, 7, 2)]
  6.     for each in win_lines:
  7.         line = board[each]
  8.         if line == "OOO" or line == "XXX":
  9.             return line[0]
  10.     return "D"
复制代码
写出它的原因是之前看到了slice这个类,它代表切片索引,比如a = slice(1, 9, 3),那么b[a]和b[1:9:3]的效果是一样
然后发现了某大神的代码
  1. def checkio(result):
  2.     rows = result
  3.     cols = map(''.join, zip(*rows))
  4.     diags = map(''.join, zip(*[(r[i], r[2 - i]) for i, r in enumerate(rows)]))
  5.     lines = rows + list(cols) + list(diags)
  6. &#8203;
  7.     return 'X' if ('XXX' in lines) else 'O' if ('OOO' in lines) else 'D'
复制代码
才想起来根本没必要用slice(完全是想多了),代码改成这样
  1. def checkio_2(game_result):
  2.     board = "".join(game_result)
  3.     win_lines = [board[0:3], board[3:6], board[6:9],
  4.                  board[0:9:3], board[1:9:3], board[2:9:3],
  5.                  board[0:9:4], board[2:7:2]]
  6.     return 'X' if ('XXX' in win_lines) else 'O' if ('OOO' in win_lines) else 'D'
复制代码
另外还学到一个用法(虽然以前就知道*的用法但是依然学到了),看下面zip的用法,对于*不懂的同学,请看甲鱼小哥视频中函数参数的收集参数
  1. >>> test = [list(range(3)), list(range(3, 6)), list(range(6, 9))]
  2. >>> test
  3. [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
  4. >>> list(zip(*test))
  5. [(0, 3, 6), (1, 4, 7), (2, 5, 8)]
复制代码
用列表来存储二维数组的鱼油都知道,有时候会想取某一列的所有元素(比如上面test的第0列就是(0, 3, 6)),那么zip的这个用法可以比如取 test第0

  1. >>> test = [list(range(3)), list(range(3, 6)), list(range(6, 9))]
  2. >>> list(zip(*test))[1]
  3. (1, 4, 7)
复制代码


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-5-22 00:10:41 | 显示全部楼层
本帖最后由 lightninng 于 2015-5-22 09:41 编辑

今天做了一个稍微难一点的chekio,来看看
索菲亚的机器人是有灵魂而且不是愚蠢的;他们可以拥有朋友和交朋友。事实上,他们已经正在为自己的并且只是为了机器人的社交网络工作!索菲亚已经收到有关机器人之间的关系的数据,她想更多地了解它们之间的关系。
我们有在机器人名字之间的用直线连线所组成的数组。 每个连接都被表示为一个包含由连字符隔开的两个机器人的名字的字符串。 例如:"dr101-mr99" 指的是 dr101mr99 是朋友。 你需要写一个函数来确定机器人之间更复杂的关系。你将得到两个名字,尝试确定它们是通过共同纽带产生关系。例如:如果两个机器人有一个共同的朋友,或者他们的朋友拥有共同的朋友等等。
56ae9e66-ca49-413f-8ecc-1b4989178f1b.png
让我们看一下例子:
scout2scout3 有共同的朋友 scout1 所以他们是有关系的。 superscout2 是通过 sscoutscout4scout1来产生关系的。 但是 dr101sscout 是没有关系的。
输入: 三个参数:朋友的信息作为一个字符串元组;第一个名字(字符串形式);第二个名字(字符串形式)。
输出: 这两个机器人是否有关系。(bool)
范例:
check_connection(
    ("dr101-mr99", "mr99-out00", "dr101-out00", "scout1-scout2",
     "scout3-scout1", "scout1-scout4", "scout4-sscout", "sscout-super"),
    "scout2", "scout3") == True
check_connection(
    ("dr101-mr99", "mr99-out00", "dr101-out00", "scout1-scout2",
     "scout3-scout1", "scout1-scout4", "scout4-sscout", "sscout-super"),
    "dr101", "sscout") == False

如何使用: 这个概念将帮助你找到纽带网络中没有过于明显的关系,还有如何运作社会网络。
前提: len(network) ≤ 45
如果 "name1-name2"网络中,那么 "name2-name1" 就不在 网络
3 ≤ len(drone_name) ≤ 6
first_namesecond_name网络中。
先贴自己的又长又不清晰的代码:
  1. def check_connection(network, first, second):
  2.     import collections
  3.     friend_map = collections.defaultdict(set)
  4.     for friendship in network:
  5.         friendship = set(friendship.split("-"))
  6.         for each_member in friendship:
  7.             friend_map[each_member].update(friendship - {each_member})
  8.     check_status = {}
  9.     for robot in friend_map:
  10.         check_status[robot] = False
  11.     check_status[first] = True
  12.     if second in friend_map[first]:
  13.         return True
  14.     path = [(first, list(friend_map[first]))]
  15.     while path:
  16.         cur_robot, friends = path.pop(-1)
  17.         next_robot = friends.pop(-1)
  18.         if friends:
  19.             path.append((cur_robot, friends))
  20.         if not check_status[next_robot]:
  21.             if second in friend_map[next_robot]:
  22.                 return True
  23.             check_status[next_robot] = True
  24.             path.append((next_robot, list(friend_map[next_robot])))
  25.     return False
复制代码
看到这个checkio,短暂的思考后最先想到的是深度搜索算法DFS(数据结构中图的内容,不明就里的同学请百度“深度优先搜索 广度优先搜索”),应该有递归和非递归两种方式,那这个思路很明确:
1、构造相邻矩阵:这里我用字典的方式,因为直接用二维列表的话会有很多多余的项
2、进行深度优先搜索:这里两点要注意,一是经过的点一定要标记已遍历;二是将路过的每个点进栈,同时用一种方法标识和这点相连的哪些点还未尝试过。因为我的语文可能比较渣,看不懂的鱼油可以研究深度优先算法,自行感受~
让我们来看看checkio里面清楚而明确的DFS算法(递归版):
  1. def build_graph(network):
  2.     #  建立相邻矩阵
  3.     graphs = {}
  4.     vertices = []
  5.     for node in network:
  6.         f, s = node.split('-')
  7.         if f not in vertices:
  8.             vertices.append(f)
  9.         if s not in vertices:
  10.             vertices.append(s)

  11.         if f not in graphs:
  12.             graphs[f] = []
  13.         graphs[f].append(s)

  14.         if s not in graphs:
  15.             graphs[s] = []
  16.         graphs[s].append(f)

  17.     return graphs, vertices


  18. def dfs(graphs, visited, f):
  19.     # 深度优先搜索核心代码
  20.     if not visited[f]:
  21.         visited[f] = True

  22.         friends = graphs[f]
  23.         for friend in friends:
  24.             dfs(graphs, visited, friend)


  25. def check_connection__dfs(network, first, second):
  26.     # 主程序代码
  27.     graphs, vertices = build_graph(network)
  28.     visited = {node: False for node in vertices}
  29.     dfs(graphs, visited, first)
  30.     return visited[second]
复制代码
但是实际上本题并不需要DFS的完整实现,因为DFS搜索过程中会栈中会保存搜索路径(递归算法其实也是用到了系统的栈,你可以在dfs函数中写上保存路径的代码),再回来看这个问题,从上面的图可以看出,其实只要找到first可以到达的点的集合,判断它是否包含second即可,这里为什么说到集合:1、集合无序,注意这题并不要求路径,只要看两点是否连通(通过其它点),另外对于两点直接连通关系也应该是无序的(如果 "name1-name2" 在 网络中,那么 "name2-name1" 就不在 网络中,说明两者含义相同 )2、集合的很多操作:交集&,并集|,差集-,以及一个集合是否属于另一个集合的判断(用<和>)都非常方便,来看看下面的代码(分别是solution中的第一位和第二位):
  1. # 找到所有最小生成树元素的集合,然后判断是否同时包含first和second
  2. # 时间复杂度低,但空间占的稍多,可用于多处
  3. def check_connection_first(network, first, second):
  4.     setlist = []
  5.     for connection in network:
  6.         s = ab = set(connection.split('-'))
  7.         # unify all set related to a, b
  8.         for t in setlist[:]:  # we need to use copy
  9.             if t & ab:       # check t include a, b
  10.                 s |= t
  11.                 setlist.remove(t)
  12.         setlist.append(s)    # only s include a, b
  13.     return any(set([first, second]) <= s for s in setlist)

  14. # 同上,但舍去一切不包含first, second的组合
  15. # 相比上者时间复杂度高(遍历多次),空间占的稍少,针对实际问题
  16. from itertools import chain


  17. def check_connection_second(shakehands, me, you):
  18.     hands = {tuple(pair.split('-')) for pair in shakehands}
  19.     amigos = {me}

  20.     while amigos:
  21.         pairs = {pair for pair in hands if any(one in pair for one in amigos)}
  22.         amigos = set(chain(*pairs)) - amigos
  23.         if you in amigos:
  24.             return True
  25.         hands -= pairs

  26.     return False
复制代码
第一段代码生成了所有可以相互连同的集合,也就是setlist,它当中的元素是集合,每个集合内的元素都可以相互到达,不同集合中的元素不可相互到达。第二段代码其实是应用了广度优先搜索,顺着me的所有相邻的对象铺开搜索,每次都已经用过的pair从hands中删除,每次都将已经检查过的点从amigos删除,当最后搜索完时(amigos为空时),如果you还未在amigos中出现 过说明不可到达。
请自己体会,这里几个pythonic的用法:
pairs = {pair for pair in hands if any(one in pair for one in amigos)}
if后面这句any表示,当amigos中有某个元素在pair中则为True,否则为False,用法有点出其不意(至少对我来说是这样)

amigos = set(chain(*pairs)) - amigos
这句中set(chain(*pairs))是把pairs中的所有元素放在一起然后转为集合(遇到同样的问题我可能就要用for循环了,不得不说itertools里面的几个函数十分的强大,学会了可以省很多事情),简单明了,chain函数请搜索itertools模块相关内容

另外提一下集合的相关用法,例子如下:
  1. >>> a = {1, 2, 3}
  2. >>> b = {0, 2, 3}
  3. >>> a= {1, 2, 3, 4}
  4. >>> b = {0, 2, 3, 5}
  5. >>> a & b  # 交集
  6. {2, 3}
  7. >>> a | b  # 并集
  8. {0, 1, 2, 3, 4, 5}
  9. >>> a - b  # 差集
  10. {1, 4}
  11. >>> c = {2, 3, 4}
  12. >>> c < a  # c在a中
  13. True
  14. >>> c < b  # c在b中
  15. False
复制代码







想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-3-28 17:07

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表