2016-08-29 1 views
2
Fuß

Lassen Sie uns sagen, dass ich eine Sequenz haben, die so geht:Partitionieren mit Prädikaten durch nur einen iterable

seq = (1, 1, 1, 1, 4, 6, 8, 4, 3, 3, 3,) 

Einige beliebige Anzahl von 1s, durch eine beliebige Anzahl von geraden Zahlen, gefolgt von einigen 3s gefolgt. Wenn ich versuche, es wie so aufzuspalten:

it = iter(seq) 
ones = list(takewhile(lambda x: x == 1, it)) 
evens = list(takewhile(lambda x: x%2 == 0, it)) 
threes = list(takewhile(lambda x: x == 3, it)) 

Diese fast klappt ... außer ich die erste gerade Zahl und die ersten drei verpassen, da sie bereits von takewhile verbraucht wird. Gibt es eine Möglichkeit, diese Art von Partitionierung durchzuführen, indem der Iterator einfach vorwärts bewegt wird, Prädikat durch Prädikat?

+0

Ich denke, du werde dieses Problem mit allem, was haben, in 'itertools' weil es _has_ auf dem nächsten Wert, um zu sehen, ob es das Prädikat übereinstimmt, aber das verbraucht es, wie Sie sagte. Wenn Sie eine benutzerdefinierte Lösung geschrieben haben, könnten Sie eine Funktion schreiben, die eine Liste und den ersten nicht übereinstimmenden Wert zurückgibt, anstatt sie zu löschen. Wenn Sie jedoch bei Generatoren bleiben müssen, müssen Sie möglicherweise kreativ werden oder eine Klasse zum Speichern verwenden. –

+1

Haben Sie "itertools.groupby" angeschaut? – BrenBarn

+0

@BrenBarn Ich habe mehrere, möglicherweise nicht disjunkte Prädikate. Ich gruppiere nicht nach einem Schlüssel. – Barry

Antwort

1

Man könnte so etwas tun:

def multi_takewhile(predicates, iterable): 
    ipredicates = iter(predicates) 
    predicate = next(ipredicates) 

    last_chunk = [] 

    for element in iterable: 
     while not predicate(element): 
      yield last_chunk 

      last_chunk = [] 

      try: 
       predicate = next(ipredicates) 
      except StopIteration: 
       break 

     last_chunk.append(element) 

Es hat immer noch das Problem des letzten Elements raubend, wenn Sie aus Prädikaten laufen, though. Sie können die Funktion so ändern, dass das letzte Element in einer anderen Liste zurückgegeben wird, oder Sie können einen eigenen iterierbaren Wrapper erstellen, der das letzte Element für Sie protokolliert.

Ein andere, itertools Weg, es zu tun mit groupby sein könnte:

import itertools 

class Grouper(object): 
    def __init__(self, predicates): 
     self.predicates = iter(predicates) 
     self.predicate = next(self.predicates) 
     self.key = 0 

    def __call__(self, element): 
     if not self.predicate(element): 
      self.key += 1 
      self.predicate = next(self.predicates) 

     return self.key 

def multi_takewhile(predicates, iterable): 
    for _, group in itertools.groupby(iterable, Grouper(predicates)): 
     yield tuple(group) 

seq = [1, 1, 1, 1, 4, 6, 8, 4, 3, 3, 3] 
ones, evens, threes = multi_takewhile([(lambda x: x == 1), (lambda x: x%2 == 0), (lambda x: x == 3)], seq) 
1

groupby wird für beliebige Tastenfunktionen arbeiten hier mit einer sorgfältig erarbeiteten Schlüsselfunktion:

def f1(x): return x == 1 
def f2(x): return x%2 == 0 
def f3(x): return x == 3 
fs = [f1, f2, f3] 

def keyfunc(x): return next((f for f in fs if f(x)), None) 

for k, vals in itertools.groupby(data, keyfunc): 
    assert k in {f1, f2, f3, None} 
    print k, vals 

Dieser Wille offensichtlich manchmal erstellen wiederholte Partitionen, zum Beispiel in dem Fall [1, 1, 3, 1, 3]

+0

Sie brauchen nicht einmal so eine raffinierte Schlüsselfunktion, um diesen Fall zu behandeln (siehe meine Antwort). Eine ähnliche Technik könnte jedoch für andere Arten von Fällen nützlich sein. – BrenBarn

+0

@BrenBarn: Richtig, ich wollte hier eine allgemeine Lösung – Eric

0

Ihr Beispiel kann gehandhabt wird durch groupby:

>>> [list(g) for ix, g in itertools.groupby(seq, lambda x: 0 if x%2==0 else x)] 
[[1, 1, 1, 1], [4, 6, 8, 4], [3, 3, 3]]