2012-06-22 14 views
6

Schreiben einer allgemeinen Funktion, die über alle iterierbaren wiederkehrenden jetzt, nächste Paare iterieren kann.Python jetzt, nächste, n Iteration

def now_nxt(iterable): 
    iterator = iter(iterable) 
    nxt = iterator.__next__() 
    for x in iterator: 
     now = nxt 
     nxt = x 
     yield (now,nxt) 

for i in now_nxt("hello world"): 
    print(i) 

('h', 'e') 
('e', 'l') 
('l', 'l') 
('l', 'o') 
('o', ' ') 
(' ', 'w') 
('w', 'o') 
('o', 'r') 
('r', 'l') 
('l', 'd') 

Ich habe über den besten Weg nachgedacht, um eine Funktion zu schreiben, wo die Anzahl der Elemente in jedem Tupel eingestellt werden kann.

zum Beispiel wenn es

func("hello",n=3) 

das Ergebnis wäre:

('h','e','l') 
('e','l','l') 
('l','l','o') 

: Ich bin zu verwenden timeit, also bitte darauf hinweisen, wenn ich etwas falsch mache hier:

import timeit 

def n1(iterable, n=1): 
    #now_nxt_deque 
    from collections import deque 
    deq = deque(maxlen=n) 
    for i in iterable: 
     deq.append(i) 
     if len(deq) == n: 
      yield tuple(deq) 

def n2(sequence, n=2): 
    # now_next 
    from itertools import tee 
    iterators = tee(iter(sequence), n) 
    for i, iterator in enumerate(iterators): 
     for j in range(i): 
      iterator.__next__() 
    return zip(*iterators) 

def n3(gen, n=2): 
    from itertools import tee, islice 
    gens = tee(gen, n) 
    gens = list(gens) 
    for i, gen in enumerate(gens): 
     gens[i] = islice(gens[i], i, None) 
    return zip(*gens) 


def prin(func): 
    for x in func: 
     yield x 

string = "Lorem ipsum tellivizzle for sure ghetto, consectetuer adipiscing elit." 

print("func 1: %f" %timeit.Timer("prin(n1(string, 5))", "from __main__ import n1, string, prin").timeit(100000)) 
print("func 2: %f" %timeit.Timer("prin(n2(string, 5))", "from __main__ import n2, string, prin").timeit(100000)) 
print("func 3: %f" %timeit.Timer("prin(n3(string, 5))", "from __main__ import n3, string, prin").timeit(100000)) 

Ergebnisse:

$ py time_this_function.py 
func 1: 0.163129 
func 2: 2.383288 
func 3: 1.908363 
+0

Wahrscheinlich nicht :) –

+0

Sieht gut aus für mich. Ich könnte versuchen, die Überprüfung von len() loszuwerden, indem ich zwei Schleifen habe: eine, um die Deque mit den ersten n-1 Elementen zu primen, und dann eine Schleife, um volle Tupel zu erhalten. Aber ich könnte auch entscheiden, dass es mit nur einer Schleife besser war. –

+1

Sie könnten in Betracht ziehen, nur die Frage "wie man das macht" zu stellen und dann Ihr Ding als Antwort zu posten, anstatt es in die Frage zu stellen. –

Antwort

5

Mein Vorschlag wäre,

from collections import deque 

def now_nxt_deque(iterable, n=1): 
    deq = deque(maxlen=n) 
    for i in iterable: 
     deq.append(i) 
     if len(deq) == n: 
      yield tuple(deq) 

for i in now_nxt_deque("hello world", 3): 
    print(i) 

('h', 'e', 'l') 
('e', 'l', 'l') 
('l', 'l', 'o') 
('l', 'o', ' ') 
('o', ' ', 'w') 
(' ', 'w', 'o') 
('w', 'o', 'r') 
('o', 'r', 'l') 
('r', 'l', 'd') 
+0

+1: Dies ist eine äußerst effiziente Lösung !! – jathanism

2

Meine Lösung:

def nn(itr, n): 
    iterable = iter(itr) 

    last = tuple(next(iterable, None) for _ in xrange(n)) 
    yield last 
    for _ in xrange(len(itr)): 
     last = tuple(chain(last[1:], [next(iterable)])) 
     yield last 

Der für Python 2 gemacht wurde, wenn Sie verwenden wollen es mit Python 3 ersetzen xrange mit range.

next, hat einen großen default Parameter, die stattdessen eine StopIteration der Erhöhung zurückgegeben werden, können Sie auch diese Standardparameter auf Ihre Funktion hinzufügen wie folgt:

def nn(itr, n, default=None): 
    iterable = iter(itr) 

    last = tuple(next(iterable, default) for _ in xrange(n)) 
    yield last 
    for _ in xrange(len(itr)): 
     last = tuple(chain(last[1:], [next(iterable, default)])) 
     yield last 

ich damit einige mehr gespielt, z.B Verwendung von itr.__class__() als Standard, aber das scheint falsch für Listen und Tupel, naja, es macht nur Sinn für Strings.

+0

Dies ist definitiv eine kompakte Lösung. –

+1

'len (itr)' funktioniert nicht für Generatoren, was diesen weniger allgemeinen Zweck macht. –

+0

Sie haben Recht, also habe ich versucht, einen besseren Weg zu finden, und ich erinnerte mich an die Itertools-Dokumentation, die ich bearbeitet habe. – dav1d

5

Hier ist eine wirklich einfache Möglichkeit, es zu tun:

  • Clone Ihre Iterator n mal mit mit itertools.tee
  • Voraus die i th Iterator i mal
  • izip sie alle zusammen
import itertools 

def now_next(sequence, n=2): 
    iterators = itertools.tee(iter(sequence), n) 
    for i, iterator in enumerate(iterators): 
     for j in range(i): 
      iterator.next() 
    return itertools.izip(*iterators) 
+0

Tolle Lösung! Nur ein Gedanke: Seit du mit Iteratoren angefangen hast, wäre es vielleicht sinnvoll, bei diesem Muster zu bleiben und itertools.izip (* iterators) am Ende zurückzugeben? – jathanism

+0

@jathanism: guter Punkt. Ich wend 'nur mit 'zip' für Klarheit. Bearbeitet. – Eric

+0

@Eric, wie skalierbar wäre diese Methode? gibt es viel Aufwand beim Klonen von Iteratoren? – beoliver

1

Eine Variation Erics Technik, die

from itertools import tee, islice, izip 

def now_next(gen, n=2): 
    gens = tee(gen, n) 
    gens = list(gens) 
    for i, gen in enumerate(gens): 
    gens[i] = islice(gens[i], i, None) 
    return izip(*gens) 

for x in now_next((1,2,3,4,5,6,7)): 
    print x 
+0

Dies scheint schneller zu sein als Erics, ich habe die Funktion der Timit-Liste in der Frage hinzugefügt. - Beachten Sie, dass izip jetzt, da ich Python 3.X verwende, zip ist – beoliver

+0

[Ich kann das auf eine Zeile herunterziehen] (http://stackoverflow.com/a/11167811/102441) – Eric

0

Ein Einzeiler basierend auf cravoori Antwort verwendet Slicing:

from itertools import tee, islice, izip 

def now_next(gen, n=2): 
    return izip(*(islice(g, i, None) for i, g in enumerate(tee(gen, n)))) 
Verwandte Themen