Frage

Ich habe eine iterable von Einträgen auf dem ich ein paar einfache Statistiken zu sammeln, wie, sagen wir die Anzahl aller Zahlen durch zwei teilbar und die Anzahl aller Zahlen durch drei teilbar ist.

Meine erste Alternative, während nur einmal durch die Liste iterieren und die Liste Expansion zu vermeiden (und halten die Split-Loop Refactoring im Auge), sieht ziemlich aufgebläht:

(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

Das sieht ziemlich nett, hat aber den Nachteil, um den Ausdruck einer Liste erweitert:

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

Was ich wirklich möchte ist so etwas wie eine Funktion wie folgt aus:

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

Aber das sieht viel wie etwas, das ohne eine Funktion getan werden könnte. Die letzte Variante ist diese:

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

und während die kleinsten (und in meinem Buch wahrscheinlich die eleganteste) es fühlt sich nicht wie es die Absicht sehr gut zum Ausdruck bringt.

Also, meine Frage an Dich:

Welche Alternative gefallen Ihnen am besten, diese Art von Statistiken zu sammeln? Fühlen Sie sich frei, Ihre eigene Alternative zu liefern, wenn Sie etwas besser haben.

einige Verwirrung zu klären unter:

  • In Wirklichkeit meine Filter Prädikate sind komplexer als nur diesen einfachen Test.
  • Die Objekte I iterieren sind größer und komplexer als nur Zahlen
  • My Filterfunktionen sind unterschiedlich und schwer in ein Prädikat zu parametrieren
War es hilfreich?

Lösung

Nachdem über die Liste mehrere Male zu wiederholen ist nicht elegant IMHO.

Ich würde wahrscheinlich eine Funktion erstellen, die erlaubt zu tun:

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

Ein Ausgangspunkt wäre so etwas wie dies:

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 Rezepte" hat ein Rezept für viel wie Ihr alt4 tun.

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

Andere Tipps

Alt 4! Aber vielleicht sollten Sie den Code in eine Funktion Refactoring, das ein Argument, das die teilbare Zahl enthalten soll (zwei und drei). Und dann könnte man eine bessere Funktionsname hat.

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

könnten Sie verwenden die filter Funktion.

Es filtert eine Liste (oder streng ein iterable) eine neue Liste der Herstellung nur die Elemente enthält, für die die angegebene Funktion true ergibt.

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

Das ist gut, denn es ermöglicht Ihnen, Ihre Statistiken Logik in einer Funktion enthalten sind und die Absicht des filter sollte ziemlich klar sein.

Ich würde eine kleine Variante der (alt 4) wählen:

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

Wenn Sie ändern möchten, welche Zahl ist, deren Umsetzung an einer Stelle ändern.

Hinweis: Da Ihre Prädikate komplex sind, werden Sie wahrscheinlich wollen, anstatt lambdas sie in Funktionen definieren. Und so werden Sie wahrscheinlich das alles in einer Klasse setzen wollen, anstatt der globalen Namespace.

Nun könnte man eine Liste Verständnis / Ausdruck tun, um eine Menge von Tupeln mit dem Stat-Test in ihnen zu bekommen und dann, dass reduzieren bis auf die Summen zu erhalten.


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

Generator Ausdruck usw. verwendet werden sollte nur einmal durch den Iterator laufen bedeuten werde (es sei denn, tut etwas seltsam reduzieren?). Grundsätzlich würde werden Sie tun Karte / reduzieren ...

sind wahre booleans zu Einheit ganzer Zahlen dazu gezwungen, und falschen booleans auf Null ganzen Zahlen. Also, wenn Sie glücklich sind scipy oder numpy zu verwenden, stellt eine Reihe von ganzen Zahlen für jedes Element der Sequenz, jedes Array enthält ein Element für jedes Ihrer Tests und Summe über die Arrays. Z.

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

würde ich auf jeden Fall in einem suchen numpy Array anstelle einer iterable Liste, wenn Sie nur Zahlen haben. Sie werden in der Lage, mit ziemlicher Sicherheit zu tun, was Sie auf dem Array mit einiger prägnanten Arithmetik wollen.

Nicht so kurz und bündig wie Sie suchen, aber effizienter, es tatsächlich funktioniert mit jedem iterable, nicht nur Iterables Sie Schleife über mehrere Male, und Sie können die Dinge erweitern zu überprüfen, ohne dass es komplizieren weiter:

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

Inspiriert von dem OO-stab oben hatte ich meine Hände auf einem als auch versuchen (obwohl diese Art und Weise übertrieben für das Problem ist, ich versuche zu lösen:)

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)

Alt 3, aus dem Grunde, dass es nicht proportional zur Anzahl der „Hits“ Speicher zu verwenden ist. Bei einem pathologischen Fall wie xrange (one_trillion), viele der anderen angebotenen Lösungen würden schlecht ausfallen.

Die Idee dabei ist die Reduktion zu verwenden, um wiederholte Iterationen zu vermeiden. Auch dann, wenn dies keine zusätzliche Datenstrukturen erstellen, wenn der Speicher ein Problem für Dich ist. Sie beginnen mit einem Wörterbuch mit Zählern ({'div2': 0, 'div3': 0}) und sie entlang der Iteration erhöhen.

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

Wenn Sie etwas komplizierter als Teilern zählen wollen, wäre es angebracht, einen objektorientierter Ansatz zu verwenden (mit den gleichen Vorteilen), die Logik für Statistik Extraktion eingekapselt wird.

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

Bitte weisen darauf hin, keine Fehler.

@Henrik: Ich denke, der erste Ansatz ist weniger wartbar, da Sie die Initialisierung des Wörterbuchs an einem Ort und Update in einer anderen zu kontrollieren haben, sowie mit Zeichenkette verwenden, um jeden stat zu beziehen (statt Attribute aufweisen). Und ich glaube nicht, OO Overkill in diesem Fall ist, für die Sie die Prädikate und Objekte werden in der Anwendung kompliziert sein. In der Tat, wenn die Prädikate wirklich einfach wäre, würde ich nicht einmal die Mühe, ein Wörterbuch zu verwenden, wird eine einzige feste Größe Liste wäre gut. Prost:)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top