2008-10-01 5 views
13

У меня есть итерабельность записей, на которых я хотел бы собрать некоторую простую статистику, скажем, подсчет всех чисел, делящихся на два, и количество всех чисел, делящихся на три.Большинство pythonic способ подсчета совпадающих элементов в чем-то итерабельном

Моя первая альтернатива, в то время итерацию только через список один раз и избежать расширения списка (и сохранение split loop рефакторинга в виду), выглядит довольно раздутой:

(альт 1)

r = xrange(1, 10) 

twos = 0 
threes = 0 

for v in r: 
    if v % 2 == 0: 
    twos+=1 
    if v % 3 == 0: 
    threes+=1 

print twos 
print threes 

Это выглядит довольно хорошо, но имеет тот недостаток, разложив выражение в список:

(альт 2)

r = xrange(1, 10) 

print len([1 for v in r if v % 2 == 0]) 
print len([1 for v in r if v % 3 == 0]) 

То, что я действительно хотел бы что-то вроде функции, как это:

(альт 3)

def count(iterable): 
    n = 0 
    for i in iterable: 
    n += 1 
    return n 

r = xrange(1, 10) 

print count(1 for v in r if v % 2 == 0) 
print count(1 for v in r if v % 3 == 0) 

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

(альт 4)

r = xrange(1, 10) 

print sum(1 for v in r if v % 2 == 0) 
print sum(1 for v in r if v % 3 == 0) 

и в то время как самый маленький (и в моей книге, пожалуй, самый элегантный) он не чувствует, как он выражает намерение очень хорошо.

Итак, мой вопрос к вам:

Какая альтернатива вам больше всего нравится, чтобы собрать эти типы статистики? Не стесняйтесь предоставлять свою собственную альтернативу, если у вас есть что-то лучше.

Чтобы прояснить некоторую путаницу ниже:

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

ответ

14

Необходимость перебора по списку несколько раз не изящна ИМХО.

я бы, вероятно, создать функцию, которая позволяет делать:

twos, threes = countmatching(xrange(1,10), 
          lambda a: a % 2 == 0, 
          lambda a: a % 3 == 0) 

Отправной точкой будет что-то вроде этого:

def countmatching(iterable, *predicates): 
    v = [0] * len(predicates) 
    for e in iterable: 
     for i,p in enumerate(predicates): 
      if p(e): 
       v[i] += 1 
    return tuple(v) 

Btw, «itertools рецепты» есть рецепт делает много как ваш alt4.

def quantify(seq, pred=None): 
    "Count how many times the predicate is true in the sequence" 
    return sum(imap(pred, seq)) 
+0

Re. повторяя дважды, у него есть кое-что для него, чтобы понять его, но кроме этого, не уменьшит ли накладные расходы от итерации один раз, когда будет съедено количество выполненного не-C-кода? – 2008-10-01 12:13:26

+0

Конечно, если я только повторю, как только он работает на одноразовых итерациях тоже, duh :) Не думал, что далеко. – 2008-10-01 15:30:00

+0

Мне нравится ваше решение. Но вы говорите: «необходимость перебора по списку несколько раз не изящна». Если существует n значений и m предикатов, вы повторяете n раз по предикатам m, так в чем преимущество перед повторением m раз на n значений? Примечание. Я новичок в Python. Ура! – 2008-10-02 15:38:32

3

Вы можете использовать функцию filter.

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

r = xrange(1, 10) 

def is_div_two(n): 
    return n % 2 == 0 

def is_div_three(n): 
    return n % 3 == 0 

print len(filter(is_div_two,r)) 
print len(filter(is_div_three,r)) 

Это хорошо, поскольку это позволяет сохранить вашу логику статистика, содержащаяся в функции и намерение filter должно быть довольно ясно.

+0

не будет ли вторая печать потреблять использованный итератор и поэтому напечатает 0? – 2008-10-01 11:14:27

0

Я бы определенно посмотрел на массив numpy, а не на итерируемый список, если у вас просто номера. Вы почти наверняка сможете делать то, что хотите, с некоторой краткой арифметикой на массиве.

+0

К сожалению, это на самом деле длинный истребитель довольно больших объектов; число вещей просто для легкого чтения :) – 2008-10-01 10:54:37

1

Ну, вы можете сделать одно понимание/выражение в списке, чтобы получить набор кортежей с этим тестом stat в них, а затем уменьшить это, чтобы получить суммы.


r=xrange(10) 
s=((v % 2 == 0, v % 3 == 0) for v in r) 
def add_tuples(t1,t2): 
    return tuple(x+y for x,y in zip(t1, t2)) 
sums=reduce(add_tuples, s, (0,0)) # (0,0) is starting amount 

print sums[0] # sum of numbers divisible by 2 
print sums[1] # sum of numbers divisible by 3 
 

Используя генератор выражений и т.д. должны означать, что вы будете работать только через итератор один раз (если не уменьшить делает ничего странного?). В основном вы будете делать карту/уменьшить ...

+0

Hah! Я знал, что для этого можно использовать сокращение :) – 2008-10-01 12:15:04

0

Не так уж и краток, как вы ищите, но более эффективный, он действительно работает с любыми итерабельными, а не только с итерациями, которые вы можете перебирать несколько раз, и вы можете расширить вещи, подлежащие проверке, без усложнения:

r = xrange(1, 10) 

counts = { 
    2: 0, 
    3: 0, 
} 

for v in r: 
    for q in counts: 
     if not v % q: 
      counts[q] += 1 
     # Or, more obscure: 
     #counts[q] += not v % q 

for q in counts: 
    print "%s's: %s" % (q, counts[q]) 
6

Alt 4! Но, возможно, вам следует переформатировать код функции, которая принимает аргумент, который должен содержать делимое число (два и три). И тогда у вас может быть лучшее имя функции.

def methodName(divNumber, r): 
    return sum(1 for v in r if v % divNumber == 0) 


print methodName(2, xrange(1, 10)) 
print methodName(3, xrange(1, 10)) 
+0

«Настоящие» тесты, к сожалению, немного отличаются от этого. Параметрирование их только дало бы мне головную боль :) – 2008-10-01 12:14:14

0
from itertools import groupby 
from collections import defaultdict 

def multiples(v): 
    return 2 if v%2==0 else 3 if v%3==0 else None 
d = defaultdict(list) 

for k, values in groupby(range(10), multiples): 
    if k is not None: 
     d[k].extend(values) 
+0

Прохладный вариант, хотя статистика не обновляется правильно, когда предмет делится на два и три. – 2008-10-01 15:26:24

0

Идея заключается в том, чтобы использовать сокращение, чтобы избежать повторных итераций. Кроме того, это не создает никаких дополнительных структур данных, если для вас проблема. Вы начинаете со словаря со своими счетчиками ({'div2': 0, 'div3': 0}) и увеличиваете их по итерации.

def increment_stats(stats, n): 
    if n % 2 == 0: stats['div2'] += 1 
    if n % 3 == 0: stats['div3'] += 1 
    return stats 

r = xrange(1, 10) 
stats = reduce(increment_stats, r, {'div2': 0, 'div3': 0}) 
print stats 

Если вы хотите посчитать что-нибудь более сложное, чем делители, было бы целесообразно использовать более объектно-ориентированный подход (с теми же преимуществами), герметизирующая логикой для извлечения статистики.

class Stats: 

    def __init__(self, div2=0, div3=0): 
     self.div2 = div2 
     self.div3 = div3 

    def increment(self, n): 
     if n % 2 == 0: self.div2 += 1 
     if n % 3 == 0: self.div3 += 1 
     return self 

    def __repr__(self): 
     return 'Stats(%d, %d)' % (self.div2, self.div3) 

r = xrange(1, 10) 
stats = reduce(lambda stats, n: stats.increment(n), r, Stats()) 
print stats 

Укажите, пожалуйста, любые ошибки.

@Henrik: Я считаю, что первый подход менее обслуживаем, поскольку вам нужно контролировать инициализацию словаря в одном месте и обновлять в другом, а также использовать строки для ссылки на каждый стат (вместо наличия атрибутов) , И я не думаю, что OO в этом случае слишком велико, поскольку вы сказали, что предикаты и объекты будут сложными в вашем приложении. На самом деле, если предикаты были действительно просты, я бы даже не стал использовать словарь, один список фиксированных размеров был бы в порядке. Ура :)

0

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

class Stat(object): 
    def update(self, n): 
    raise NotImplementedError 

    def get(self): 
    raise NotImplementedError 


class TwoStat(Stat): 
    def __init__(self): 
    self._twos = 0 

    def update(self, n): 
    if n % 2 == 0: self._twos += 1 

    def get(self): 
    return self._twos 


class ThreeStat(Stat): 
    def __init__(self): 
    self._threes = 0 

    def update(self, n): 
    if n % 3 == 0: self._threes += 1 

    def get(self): 
    return self._threes 


class StatCalculator(object): 
    def __init__(self, stats): 
    self._stats = stats 

    def calculate(self, r): 
    for v in r: 
     for stat in self._stats: 
     stat.update(v) 
    return tuple(stat.get() for stat in self._stats) 


s = StatCalculator([TwoStat(), ThreeStat()]) 

r = xrange(1, 10) 
print s.calculate(r) 
1

Правда booleans принуждаются к целым числам единицы, а false - логическим значениям до нулевых целых чисел. Поэтому, если вы счастливы использовать scipy или numpy, создайте массив целых чисел для каждого элемента вашей последовательности, каждый массив, содержащий один элемент для каждого из ваших тестов, и суммируйте по массивам. Например.

>>> sum(scipy.array([c % 2 == 0, c % 3 == 0]) for c in xrange(10)) 
array([5, 4]) 
0

Alt 3, по причине того, что он не использует память пропорционально количеству «хитов». Учитывая такой патологический случай, как xrange (one_trillion), многие другие предлагаемые решения потерпят неудачу.

2

я бы выбрал небольшой вариант вашего (альт 4):

def count(predicate, list): 
    print sum(1 for x in list if predicate(x)) 

r = xrange(1, 10) 

count(lambda x: x % 2 == 0, r) 
count(lambda x: x % 3 == 0, r) 
# ... 

Если вы хотите изменить то, что граф делает, изменить его реализацию в одном месте.

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