Python谜题代码审查(剧透)
我一直在研究一个叫做Python Challenge的项目里的问题。其中一个问题是要从一堆杂乱的字符中找出最稀有的字符。
我的方法是先从一个文本文件中读取这些字符,然后把字符和它们出现的次数存储在一个字典里,字典的形式是键值对。接着,我会根据出现次数对字典进行排序,并把字典反转,变成以出现次数为键,字符串为值的形式。假设最稀有的字符只出现一次,我就返回这个反转字典中键为一的值。
输入的文件(funkymess.txt)内容大概是这样的:
%%$@$^_#)^)&!_+]!*@&^}@@%%+$&[(_@%+%$*^@$^!+]!&#)*}{}}!}]$[%}@[{@#_^{*......
代码如下:
from operator import itemgetter
characterDict = dict()
#put the characters in a dictionary
def putEncounteredCharactersInDictionary(lineStr):
for character in lineStr:
if character in characterDict:
characterDict[character] = characterDict[character]+1
else:
characterDict[character] = 1
#Sort the character dictionary
def sortCharacterDictionary(characterDict):
sortCharDict = dict()
sortsortedDictionaryItems = sorted(characterDict.iteritems(),key = itemgetter(1))
for key, value in sortsortedDictionaryItems:
sortCharDict[key] = value
return sortCharDict
#invert the sorted character dictionary
def inverseSortedCharacterDictionary(sortedCharDict):
inv_map = dict()
for k, v in sortedCharDict.iteritems():
inv_map[v] = inv_map.get(v, [])
inv_map[v].append(k)
return inv_map
f = open('/Users/Developer/funkymess.txt','r')
for line in f:
#print line
processline = line.rstrip('\n')
putEncounteredCharactersInDictionary(processline)
f.close()
sortedCharachterDictionary = sortCharacterDictionary(characterDict)
#print sortedCharachterDictionary
inversedSortedCharacterDictionary = inverseSortedCharacterDictionary(sortedCharachterDictionary)
print inversedSortedCharacterDictionary[1]r
有没有人能帮我看看,我的思路是否正确?如果可以的话,还希望能给我一些关于优化、最佳实践和可能的重构建议,无论是从语言的角度还是算法的角度。
谢谢!
5 个回答
这段代码实在是太多了。
[k for k, v in characterdict.iteritems()
if v = min(characterdict.items(), key=operator.itemgetter(1))[0]]
可以根据需要进行优化(比如先把最小值存到另一个变量里)。
其实你不需要写那么多代码,因为Python里面已经有一个类可以帮你数可迭代对象里的元素了!下面的代码就能完成你想要的所有功能。
from collections import Counter
counter = Counter(open(<...>).read())
print min(counter, key=counter.get)
解释一下:
collections
是Python的一个标准模块,里面包含了一些常用的数据结构。特别是,它里面有一个叫Counter
的东西,这个是dict
的一个子类,专门用来统计东西出现的频率。它可以接收一个可迭代对象,然后数里面所有的字符。
你可能知道,在Python中,字符串是可迭代的,而它的元素就是单个字符。所以我们可以open
文件,一次性read
里面的所有内容,然后把这个大字符串传给Counter
。这样就会生成一个类似字典的对象,把字符和它们的出现频率对应起来。
最后,我们想要找到出现频率最低的字符,也就是在这个频率字典中找最小的元素。换句话说,我们想要根据字典里的值找到counter
的最小元素。Python有一个内置函数专门用来找最小值,叫min
。如果你想根据某个条件来排序数据,可以给它传一个可选的key参数,这样它就会根据这个key来排序列表。在这种情况下,我们让min
根据counter.get
来找最小元素;换句话说,我们是根据字符的频率来排序的!
重构:一步一步来
我想带你了解一下重构的过程。学习编程不仅仅是知道最终的结果,这通常是你在Stack Overflow上提问时得到的答案。更重要的是,学会如何自己找到这个答案。当人们对这样的问题给出简短而密集的回答时,往往不容易看出他们是怎么得出这些解决方案的。
所以让我们来进行一些重构,看看如何简化你的代码。我们会重写、删除、重命名和重新排列代码,直到没有更多的改进空间。
简化你的算法
在Python中,代码不需要写得那么冗长。当你在Python中使用显式循环处理列表和字典时,通常是个不好的迹象,应该考虑使用列表推导式和可以对整个容器操作的函数。
使用defaultdict来存储字符计数
defaultdict(int)
会在访问时生成条目,如果它们不存在的话。这让我们在计算字符时可以省去if/else的判断。
from collections import defaultdict
characterDict = defaultdict(int)
def putEncounteredCharactersInDictionary(lineStr):
for character in lineStr:
characterDict[character] += 1
排序字典
字典中的键没有保证顺序。你不能假设插入的顺序就是存储的顺序。因此,排序字典条目后再放回另一个字典,只会让它们重新混乱。
这意味着你的函数基本上是无效的。在你排序条目后,需要将它们保持为元组列表,以保留排序顺序。去掉那些代码后,我们可以将这个方法简化为一行。
def sortCharacterDictionary(characterDict):
return sorted(characterDict.iteritems(), key=itemgetter(1))
反转字典
根据之前的评论,排序后你实际上不会再有字典了。但假设你有,这个函数就是一个不鼓励使用显式循环的例子。在Python中,始终要考虑如何一次性对集合进行操作,而不是逐个处理。
def inverseSortedCharacterDictionary(sortedCharDict):
return dict((v, k) for k, v in sortedCharDict.iteritems())
我们可以在一行中完成(1)遍历字典中的键/值对;(2)交换它们并创建反转的值/键元组;(3)从这些反转的元组创建一个字典。
明智地注释和命名
你的方法名称很长且描述性强。没有必要在注释中重复相同的信息。只有当你的代码不够自解释时,比如有复杂的算法或不明显的结构,才需要使用注释。
在命名方面,你的名称不必要地长。我建议使用更简短的名称,并使其更通用。比如,不要用inverseSortedCharacterDictionary
,可以试试invertedDict
。这个方法的作用就是反转一个字典。它传入的字典是否是排序的字符字典并不重要。
作为一个经验法则,尽量使用最通用的名称,这样你的方法和变量就可以更通用。越通用意味着越可重用。
characters = defaultdict(int)
def countCharacters(string):
for ch in string:
characters[ch] += 1
def sortedCharacters(characters):
return sorted(characters.iteritems(), key=itemgetter(1))
def invertedDict(d):
return dict((v, k) for k, v in d.iteritems())
减少代码量
使用临时变量和辅助方法是个好编程习惯,我为你在程序中这样做感到高兴。然而,现在这些函数已经简化到每个只有一两行,我们可能不再需要它们了。
这是在上面修改函数后的程序主体:
f = open('funkymess.txt', 'r')
for line in f:
countCharacters(line.rstrip('\n'))
f.close()
print sortedCharacters(characters)[0]
接下来,我们就把这些辅助方法内联,因为它们实在太简单了。经过所有重构后的最终程序是:
最终程序
#!/usr/bin/env python
from operator import itemgetter
from collections import defaultdict
characters = defaultdict(int)
f = open('funkymess.txt','r')
for line in f:
for ch in line.rstrip('\n'):
characters[ch] += 1
f.close()
print sorted(characters.iteritems(), key=itemgetter(1))[0]