Подтвердить что ты не робот

Самый пифонический способ подсчета совпадающих элементов в чем-то итерабельном

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

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

(alt 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

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

(alt 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])

Мне бы очень понравилась функция вроде:

(alt 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)

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

(alt 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)

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

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

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

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

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

Ответ 1

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

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

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 recipes" имеет рецепт для того, чтобы делать так же, как ваш alt4.

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

Ответ 2

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))

Ответ 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 должна быть довольно понятной.

Ответ 4

Я бы выбрал небольшой вариант вашего (alt 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)
# ...

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

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

Ответ 5

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


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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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])

Ответ 9

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)

Ответ 10

Идея здесь заключается в использовании сокращения, чтобы избежать повторных итераций. Кроме того, это не создает никаких дополнительных структур данных, если для вас проблема. Вы начинаете со словаря со своими счетчиками ({'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: Я думаю, что первый подход менее удобен в обслуживании, поскольку вам нужно контролировать инициализацию словаря в одном месте и обновлять в другом, а также использовать строки для ссылки на каждый stat (вместо наличия атрибутов). И я не думаю, что OO в этом случае слишком велико, поскольку вы сказали, что предикаты и объекты будут сложными в вашем приложении. На самом деле, если предикаты были действительно просты, я бы даже не стал использовать словарь, один список фиксированных размеров был бы в порядке. Приветствия:)

Ответ 11

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

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)

Ответ 12

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