Обзор кода Python Puzzle (спойлер) - PullRequest
4 голосов
/ 11 ноября 2010

Я работал над проблемами, представленными в 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 ]

7 голосов
/ 11 ноября 2010

Рефакторинг: прохождение

Я хочу провести вас через процесс рефакторинга. Обучение программированию - это не просто знание конечного результата, который вы обычно получаете, когда задаете вопрос о переполнении стека. Речь идет о том, как добраться до этого ответа самостоятельно. Когда люди публикуют короткие и точные ответы на подобные вопросы, не всегда очевидно, как они пришли к своим решениям.

Итак, давайте проведем некоторый рефакторинг и посмотрим, что мы можем сделать, чтобы упростить ваш код. Мы переписываем, удаляем, переименовываем и переставляем код до тех пор, пока больше не будет выполнено никаких улучшений.

Упростите ваши алгоритмы

Python не должен быть таким многословным. Обычно это пахнет кодом, когда у вас есть явные циклы, работающие над списками и диктантами в Python, вместо того, чтобы использовать списки и функции, которые работают с контейнерами в целом.

Использовать defaultdict для хранения количества символов

A 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) перебираем пары ключ / значение в dict; (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]
4 голосов
/ 11 ноября 2010

Вам даже не нужно столько кода, как это, потому что в 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 голосов
/ 11 ноября 2010

Это слишком много кода.

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

Оптимизировать по желанию (например, сначала сохранить минимум в другой переменной).

1 голос
/ 11 ноября 2010

Вот код, который я использовал для решения этой загадки:

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])
0 голосов
/ 11 ноября 2010

Другой способ (не очень компактный) для выполнения вашей задачи:

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)]
>>> 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...