我一直在研究Python Challenge中提出的问题。其中一个问题是要筛选出一堆乱七八糟的角色并挑出最稀有的角色。
我的方法是从文本文件中读取字符,将字符/出现作为键/值对存储在字典中。按值对字典进行排序,并将出现为键的字典反转,字符串为值。假设最稀有的字符只出现一次,我返回这个倒置字典的键等于1的值。
输入(funkymess.txt)是这样的:
%% $ @ $ ^ _#)^)及!_ +] * @&安培; ^} @@ %% + $&安培; [(_ @%+%$ * ^ @ $ ^ +]&安培;!<!EM>#) *} {}}} ] $ [%} @ [{ @#_ ^ {* .. ....
代码如下:
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
有人可以看看并向我提供一些指示,说明我是否在这里正确的轨道,如果可能的话,从语言和算法的角度提供一些关于可能的优化/最佳实践和潜在重构的反馈
由于
答案 0 :(得分:7)
我想引导您完成重构过程。学习编程不仅仅是了解最终结果,这是您在Stack Overflow上提出问题时通常会得到的结果。这是关于如何自己获得答案的。当人们对这样的问题发表简短而密集的答案时,他们如何达到他们的解决方案并不总是很明显。
让我们进行一些重构,看看我们可以做些什么来简化代码。我们将重写,删除,重命名和重新排列代码,直到不再进行任何改进为止。
Python不需要那么冗长。当您在Python中使用列表和dicts进行显式循环时,通常会出现代码异味,而不是使用对容器作为整体进行操作的列表推导和函数。
defaultdict(int)
会在访问条目时生成条目(如果它们不存在)。这让我们在计算字符时消除if / else分支。
from collections import defaultdict
characterDict = defaultdict(int)
def putEncounteredCharactersInDictionary(lineStr):
for character in lineStr:
characterDict[character] += 1
字典不保证对其密钥进行任何排序。您不能假设这些项目的存储顺序与您插入它们的顺序相同。因此,对dict条目进行排序,然后将它们放回到另一个dict中,只需将它们拼凑起来即可。
这意味着您的功能基本上是无操作。对项目进行排序后,您需要将它们保留为元组列表以保留其排序顺序。删除该代码后,我们可以将此方法减少到一行。
def sortCharacterDictionary(characterDict):
return sorted(characterDict.iteritems(), key=itemgetter(1))
鉴于之前的评论,您在排序后将不再拥有dict。但假设您这样做,此函数是不鼓励显式循环的情况之一。在Python中,始终在思考如何一次操作集合而不是一次操作集合。
def inverseSortedCharacterDictionary(sortedCharDict):
return dict((v, k) for k, v in sortedCharDict.iteritems())
在一行中我们(1)遍历dict中的键/值对; (2)切换它们并创建反转值/键元组; (3)用这些倒置的元组创建一个字典。
您的方法名称很长且具有描述性。没有必要在评论中重复相同的信息。仅当您的代码不具有自我描述性时才使用注释,例如当您拥有复杂的算法或不明显的异常构造时。
在命名方面,你的名字不必要很长。我会坚持使用 less 描述性名称,并使它们更通用。而不是inverseSortedCharacterDictionary
,请尝试invertedDict
。这就是所有方法所做的,它颠倒了一个字典。如果它传递了排序的字符dict或任何其他类型的字典,那实际上并不重要。
根据经验,尝试使用最通用的名称,以便您的方法和变量尽可能通用。更通用意味着更可重用。
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]
答案 1 :(得分: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
对列表进行排序。在这种情况下,我们要求min
找到按counter.get
排序的最小元素;换句话说,我们按其频率排序!
答案 2 :(得分:2)
代码太多了。
[k for k, v in characterdict.iteritems()
if v = min(characterdict.items(), key=operator.itemgetter(1))[0]]
根据需要进行优化(例如,首先将最小值存储在另一个变量中)。
答案 3 :(得分:1)
以下是我用来解决这个难题的代码:
comment = open('comment.txt').read()
for c in sorted(set(comment)):
print ' %-3s %6d' % (repr(c)[1:-1], comment.count(c))
它按字母顺序而不是按频率对字符进行排序,但最稀有的字符很容易从输出中获取。
如果我想要频率排序,我会使用集合。像katrielalex建议的那样(如果我记得它的存在),或者
from collections import defaultdict
comment = open('comment.txt').read()
counts = defaultdict(int)
for c in comment:
counts[c] += 1
for c in sorted(counts, key=counts.get):
print ' %-3s %6d' % (repr(c)[1:-1], counts[c])
答案 4 :(得分:0)
完成任务的另一种方式(非常紧凑):
text = """%$@$^_#)^)&!_+]!*@&^}@@%%+$&[(_@%+%$*^@$^!+]!&#)*}{}}!}"""
chars = set(text)
L = [[c, text.count(c)] for c in chars]
L.sort(key=lambda sublist: sublist[1])
>>> L
[('(', 1),
('[', 1),
('{', 1),
('#', 2),
(']', 2),
(')', 3),
('*', 3),
('_', 3),
('&', 4),
('+', 4),
('!', 5),
('%', 5),
('$', 5),
('}', 5),
('^', 5),
('@', 6)]
>>>