Python拼图代码评论(剧透)

时间:2010-11-11 16:30:48

标签: python algorithm

我一直在研究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

有人可以看看并向我提供一些指示,说明我是否在这里正确的轨道,如果可能的话,从语言和算法的角度提供一些关于可能的优化/最佳实践和潜在重构的反馈

由于

5 个答案:

答案 0 :(得分:7)

重构:演练

我想引导您完成重构过程。学习编程不仅仅是了解最终结果,这是您在Stack Overflow上提出问题时通常会得到的结果。这是关于如何自己获得答案的。当人们对这样的问题发表简短而密集的答案时,他们如何达到他们的解决方案并不总是很明显。

让我们进行一些重构,看看我们可以做些什么来简化代码。我们将重写,删除,重命名和重新排列代码,直到不再进行任何改进为止。

简化算法

Python不需要那么冗长。当您在Python中使用列表和dicts进行显式循环时,通常会出现代码异味,而不是使用对容器作为整体进行操作的列表推导和函数。

使用defaultdict存储字符数

defaultdict(int)会在访问条目时生成条目(如果它们不存在)。这让我们在计算字符时消除if / else分支。

from collections import defaultdict
characterDict = defaultdict(int)

def putEncounteredCharactersInDictionary(lineStr):
    for character in lineStr:
        characterDict[character] += 1

排序dicts

字典不保证对其密钥进行任何排序。您不能假设这些项目的存储顺序与您插入它们的顺序相同。因此,对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)]
>>>