2013-09-24 2 views
0

Предположим, что у меня есть код вдоль этих линий:определении количества значений получены с помощью генератора

counter = Counter() 
text = f.read() 
words = words_generator(text) 
interesting_words = filter_generator(words) 
counter.update(interesting_words) 

for i in counter: 
    print("Frequency for "+i ": "+counter[i]/sum) 

Как мне лучше установить значение sum которое число значений по words_generator выданное?

ответ

4
from collections import Counter 

class CountItemsWrapper: 
    def __init__(self, items): 
     self.items = iter(items) 
     self.count = 0 

    def __next__(self): 
     res = next(self.items) 
     self.count += 1 
     return res 

    def __iter__(self): 
     return self 

counter = Counter() 
text = f.read() 
words = CountItemsWrapper(words_generator(text)) 
interesting_words = filter_generator(words) 
counter.update(interesting_words) 

for i in counter: 
    print("Frequency for "+i ": "+counter[i]/words.count) 

В основном, CountItemsWrapper это итератор, который просто проходит через значение, но сохраняет рассчитывать всякий раз, когда это делает.

Затем вы можете использовать атрибут count на обертке как ваш sum.


Объяснение класса:

def __init__(self, items): 
    self.items = iter(items) 
    self.count = 0 

Это просто. Имейте в виду, что экземплярами являются итераторы, а не только итерации. Так это итерации один раз, держа счет один раз.


def __next__(self): 
    res = next(self.items) 
    self.count += 1 
    return res 

Это называется, чтобы получить следующий пункт. self.count должен быть добавлен после вызов next, потому что мы разрешаем StopIteration распространяться и не хотим добавлять к счету, если мы не дали значение.


def __iter__(self): 
    return self 

Это итератор так он возвращает себя.

2

Q & D posssible техническое решение: заворачивайте генератор в качестве итератора, который отслеживает количество элементов seens, то есть:

class IterCount(object): 
    def __init__(self, iterable): 
     self._iterable = iterable 
     self._count = 0 

    def _itercount(self): 
     for value in self._iterable: 
      self._count += 1 
      yield value 

    def __iter__(self): 
     return self._itercount() 

    @property 
    def count(self): 
     return self._count 


itc1 = IterCount(range(10)) 
print list(itc1) 
print itc1.count 

itc2 = IterCount(xrange(10)) 
print list(itc2) 
print itc2.count 
+0

Вы можете просто поместить код '_itercount' внутри' __iter__' (да, '__iter__' может быть генератором). – Bakuriu

+0

Я не понимаю, почему вы сделали это повторным, поскольку счетчик не сбрасывается. Это означает, что он «сломан», если вы используете его в нескольких циклах, я думаю. Вы должны переместить 'self._count' из переменной класса в переменную на итераторе. – Veedrac

+0

@Veedrac Кто сказал, что он повторим? Этот код в точности эквивалентен вашему. Единственное различие заключается в том, что он использует генератор, чтобы избежать определения '__next__' (и терпит неудачу в его назначении, так как он определяет другой бесполезный метод для этого, чего я не понимаю. Либо укажите' __iter__' + '__next__ 'или' __iter__' в качестве генератора.) – Bakuriu

0

Самое простое решение заключается в создании списка:

words = list(words_generator(text)) 

Другим вариантом является использование itertools.tee:

words, words_copy = itertools.tee(words_generator(text)) 

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

filtered = filter_generator(words) 
total = 0 
for word, _ in zip(filtered, words_copy): # use itertools.izip in python2 
    counter[word] += 1 
    total += 1 
total += sum(1 for _ in words_copy) 

который использует в большинстве O(n-k) памяти, где n это количество слов в тексте и k этого числа интересных слов в тексте.Вы можете упростить код немного с помощью:

from itertools import zip_longest #izip_longest in python2 
filtered = filter_generator(words) 
total = 0 
for word, _ in zip_longest(filtered, words_copy): 
    counter[word] += 1 
    total += 1 
del counter[None] 

который использует только O(1) память (если генераторы постоянного пространства).

Обратите внимание, однако, что наличие явных циклов замедляет код, поэтому в конце, если память не является опцией, может быть лучшим решением для построения list для words.

Смежные вопросы