2012-04-05 18 views
5

Po pierwsze, przegląd iteratory C++ stylu quickly.for przykład:jak tworzyć bardziej ekspresyjne iteratory Pythona? podobnie jak C++ iteracyjnej

//--- Iterating over vector with iterator. 
vector<int> v; 
. . . 
for (vector<int>::iterator it = v.begin(); it!=v.end(); ++it) { 
    cout << *it << endl; 
} 

Jest elastyczny. Łatwo zmienić podstawowe typy kontenerów. Na przykład możesz później zdecydować, że liczba wstawień i usunięć jest tak wysoka, że ​​lista byłaby bardziej wydajna niż wektor. Ma także wiele przydatnych funkcji składowych. Wiele funkcji składowych wektora używa iteratorów, na przykład przypisywania, wstawiania lub kasowania. Ponadto możemy użyć iteratora (jeśli jest obsługiwany) dwukierunkowo, na przykład ++, -. Jest to przydatne do analizowania strumienia, np. Obiektów.

Problemy z pythonem: 1: Obecnie python dla składni pętli jest mniej elastyczny niż dla języka C++. (no, safer) 2: zamiast "it! = iter.end()" styl, python rzuci wyjątek, gdy next() nie ma więcej. Nie jest elastyczny.

Pytanie 1: Czy mój pomysł jest powyżej prawidłowy?

OK. Nadchodzi moje pytanie, jak zaimplementować potężniejszy iterator Pythona tak potężny jak iteratory C++? Obecnie Python dla składni pętli jest mniej elastyczny niż dla C++. Znajduję również kilka możliwych rozwiązań, takich jak http://www.velocityreviews.com/forums/t684406-pushback-iterator.html. ale prosi użytkownika o push_back rzeczy zamiast zadawania iteratora -.

Pytanie 2: Jaki jest najlepszy sposób na zastosowanie dwukierunkowego Iteratora w pythonie? Podobnie jak http://www.cplusplus.com/reference/std/iterator/BidirectionalIterator/. Pseudo-kod jest następujący:

it = v.begin(); 
while(it!=v.end()) { 
    //do sth here 

    if (condition1) 
     ++it;//suppose this iterator supports ++ 
    if(condition2) 
     --it;//suppose this iterator supports -- 
} 

Główne cechy to: 1) dwukierunkowy, 2) prostsze "koniec" sprawdzanie. Operatory "++" lub "-" lub typowe funkcje nie mają znaczenia (i tak nie ma żadnej różnicy semantycznej).

Dzięki,

Aktualizacja: Mam kilka możliwych rozwiązań z odpowiedzi:

i = 0 
while i < len(sequence): # or i < len and some_other_condition 
    star_it = sequence[i] 
    if condition_one(star_it): 
     i += 1 
    if condition_two(star_it): 
     i = max(i - 1, 0) 

Jednak w przeciwieństwie do tablicy, random access listy powinny być O (n). Przypuszczam, że obiekt "list" w pythonie wewnętrznie jest zaimplementowany przy użyciu podobnych obiektów. Tak więc to rozwiązanie pętli while nie jest wydajne. Jednak w języku C++ mamy "losowy iterator", "dwukierunkowy iterator". Jak mam uzyskać lepsze rozwiązanie? Dzięki.

+1

Czy możesz podać konkretny przykład czegoś, czego nie możesz zrobić w 'Pythonie', który możesz łatwo zrobić w' C++ '? –

+0

Mechanizm 'yield' + exception w Pythonie jest niesamowicie elastyczny (to, co nazywamy" kontynuacjami "w programowaniu funkcjonalnym). Naucz się ich właściwie używać, a otrzymasz nagrodę. Znacznie łatwiej jest definiować iteratory złożone w Pythonie niż w C++. –

+0

To pytanie jest zbyt ogólnikowe i nie ma prawdziwej odpowiedzi na pytanie w obecnej formie. –

Odpowiedz

0

W rzeczywistości system iteratorów C++ nie jest tak wspaniały. Iteratory są zbliżone do wskaźników, a oni mają swoje nieszczęścia:

  • wartości pojedynczej: v.end() nie można dereferencjonowane bezpiecznie
  • wystawianiu inwersji std::for_each(end, begin, func);
  • wystawianiu niedopasowania: std::for_each(v0.begin(), v2.end(), func);

podejście Python jest dużo lepsze pod tym względem (chociaż użycie wyjątku może być dość zaskakujące, naprawdę pomaga w definiowaniu zagnieżdżonych iteratorów), ponieważ w przeciwieństwie do jego nazwy, iterator Pythona jest bardziej podobny do Range.

Koncepcja Range jest tak dużo lepiej niż C++ 11 wprowadza rząd-do pętli konstruktem:

for (Object& o: range) { 
} 

wszystko, co jest możliwe z iteratora jest również możliwe z wielu, choć może to potrwać Czasami zdajemy sobie z tego sprawę, a niektóre tłumaczenia wydają się surrealistom na początku dla tych z nas, którzy zostali wykształceni za pomocą iteratorów typu C++. Na przykład, może być doskonale podzakresy wyrażona:

for (Object& o: slice(range, 2, 9)) { 
} 

gdzie slice miałaby wszystkie elementy w położenie [2, 9) ciągu range.

Więc zamiast walczyć język (Python) należy zagłębić się dalej w nią i objąć swój styl. Walka z językiem jest ogólnie przegraną walką, ucz się jej idiomów, stają się skuteczne.

+0

Po prostu potrzebuję dwukierunkowego Iteratora. Czy mamy tak dużo cukru gramatycznego, wygląda to na łatwe, ale nie tak głupie i przejrzyste w C++. –

5

Dla większości sytuacjach Pythona for i iteratory są najprostszą rzeczą wokół. To jest ich cel i nie powinno to zagrozić elastyczności - ich brak elastyczności nie jest problemem.

Przez kilka sytuacji, w których nie można używać do for pętlę, C++ iteratory może być prostsze. Ale zawsze jest sposób, aby to zrobić w Pythonie, który nie jest bardziej skomplikowany niż przy użyciu iteratora C++.


Jeśli trzeba oddzielić pogłębianie iteracyjnej z pętli, wystarczy użyć while pętlę:

it = iter(obj) 

try: 
    while True: # or some secondary break condition other than StopIteration 
     star_it = next(it) 
     if condition_one(star_it): 
      star_it = next(it) 
except StopIteration: 
    pass # exhausted the iterator 

mogę myśleć tylko dwie sytuacje, w których --it ma sens w Pythonie.

Pierwszy to iteracja sekwencji. W takim przypadku, jeśli trzeba iść do tyłu, nie używaj iterator w ogóle - po prostu użyć licznika z while pętli:

i = 0 
while i < len(sequence): # or i < len and some_other_condition 
    star_it = sequence[i] 
    if condition_one(star_it): 
     i += 1 
    if condition_two(star_it): 
     i = max(i - 1, 0) 

Drugi to jeśli iteracji nad podwójnie połączonej listy . W takim przypadku, ponownie, nie używaj iterator - po prostu przechodzić węzły zwykle:

current = node 
while current: # or any break condition 
    if condition_one(current): 
     current = current.next 
    if condition_two(star_it): 
     current = current.prev 

sytuacji, gdy może, że to ma sens, ale nie można skorzystać z jednej z powyższych metod , ma kolekcję nieuporządkowaną, taką jak set lub dict. Jednak w tym przypadku sens nie ma znaczenia. Ponieważ kolekcja jest nieuporządkowana, semantycznie, każdy z elementów wcześniej osiągniętych byłby odpowiedni - nie tylko aktualny poprzedni element.

Tak więc, w celu poznania odpowiedniego obiektu, aby powrócić do, trzeba pamięć, albo przez Iterowanie nad sekwencji jak mydict.values() lub tuple(myset) i za pomocą licznika, albo przez łączenie sekwencji poprzednich wartości as you go i za pomocą pętlę while i powyżej, zamiast pętli for.

+1

@ XinlinCao Ale rozwiązania dostosowane do tych sytuacji są łatwiejsze w użyciu niż rozwiązania ogólne, zwłaszcza gdy w przypadku innych sytuacji konieczne będzie zastosowanie bardziej złożonych rozwiązań. Python ma narzędzia do analizowania. Jeśli chodzi o pliki, nie widzę, jak iterator jest lepszy niż po prostu wywołanie 'seek'? – agf

+2

@ XinlinCao: Twoje przypadki użycia są zbyt nieprecyzyjne. Podaj konkretny praktyczny przypadek, a my możemy pokazać ci dobre rozwiązanie w Pythonie. –

+0

@ XinlinCao Próbujemy ci powiedzieć, że to niewłaściwe pytanie__. W Pythonie są lepsze sposoby robienia tego, co zrobiłbyś z dwukierunkowym iteratorem w C++. – agf

0

Można wdrożyć podobny sposób C++ przy użyciu obiektów Pythona:

class Iterable(object): 
    class Iterator(object): 
    def __init__(self, father, pos=0): 
     self.father = father 
     self.pos = pos 

    def __getitem__(self, pos=0): 
     return self.father[self.pos + pos] 

    def __setitem__(self, pos, value): 
     self.father[self.pos + pos] = value 

    def __iadd__(self, increment): 
     self.pos += increment 
     return self 

    def __isub__(self, decrement): 
     self.pos -= decrement 
     return self 

    def __ne__(self, other): 
     return self.father != other.father or self.pos != other.pos 

    def __eq__(self, other): 
     return not (self != other) 

    def begin(self): 
    return self.Iterator(self) 

    def end(self): 
    return self.Iterator(self, len(self)) 

class Vector(list, Iterable): 
    pass 

v = Vector([54, 43, 32, 21]) 

counter = 0 
it = v.begin() 
print it, it[0] 
while it != v.end(): 
    counter += 1 
    print it[0] 
    if counter == 2: 
    it += 1; # suppose this iterator supports ++ 
    if counter == 1: 
    it -= 1; # suppose this iterator supports -- 
    it += 1 

Zastępuje *it przez it[0] (również analogicznie do C++) i it++ przez it += 1, ale w rzeczywistości pozostaje prawie taka sama.

zostawić sposoby pythonic jeśli to zrobić, choć ;-)

1

Rozwiązania dla kilku sytuacjach wspomniałeś:

  1. Chcesz wymienić obiektów w bazowym pojemniku. Słowników, iteracyjne nad przyciskami lub elementów, nie tylko wartości:

    for key, value in my_dict.iteritems(): 
        if conditiion(value): 
         my_dict[key] = new_value 
    

    na listach używać enumerate():

    for index, item in enumerate(my_list): 
        if condition(item): 
         my_list[index] = new_item 
    
  2. Chcesz iterację z jednej wartości "look-ahead". Prawdopodobnie byłoby użyć coś dostosowane do konkretnej sytuacji, ale tutaj jest recepta na ogólnych sytuacjach:

    def iter_with look_ahead(iterable, sentinel=None): 
        iterable, it_ahead = itertools.tee(iterable) 
        next(it_ahead, None) 
        return izip_longest(iterable, it_ahead, fillvalue=sentinel) 
    
    for current, look_ahead in iter_with look_ahead(tokens): 
        # whatever 
    
  3. Chcesz iteracyjne w odwrotnej kolejności. Użyj kontenerów, które go obsługują.

  4. Chcesz uzyskać dostęp losowy. Wystarczy włączyć iterowalny do listy i użyć indeksów:

    my_list = list(my_iterable) 
    
0

Należy pamiętać, że obiekt listy w Pythonie jest tablicą, więc koncern sprawność wspomniano w pytaniu jest faktycznie nie problem.

+1

Powinno to być w sekcji komentarza zamiast rozwiązania. – DaveyLaser