Python谜题代码审查(剧透)

4 投票
5 回答
1117 浏览
提问于 2025-04-16 06:55

我一直在研究一个叫做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 个回答

2

这段代码实在是太多了。

[k for k, v in characterdict.iteritems()
  if v = min(characterdict.items(), key=operator.itemgetter(1))[0]]

可以根据需要进行优化(比如先把最小值存到另一个变量里)。

4

其实你不需要写那么多代码,因为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来找最小元素;换句话说,我们是根据字符的频率来排序的!

7

重构:一步一步来

我想带你了解一下重构的过程。学习编程不仅仅是知道最终的结果,这通常是你在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]

撰写回答