2017-10-25 5 views
2

私は反復可能なオブジェクト内のすべての要素を互いに組み合わせて比較したいと思います。以下の再現可能な例は単純なリストの機能を模倣していますが、私の問題を示しています。この例では、["A"、 "B"、 "C"、 "D"]のリストで、次の16行の出力を得たいと思います。 100項目のリストは、100 * 100 = 10,000行を生成します。どのようにして、同時に1つのpython iterableに複数のイテレータを持たせることができますか?

A A True 
A B False 
A C False 
... 10 more lines ... 
D B False 
D C False 
D D True 

次のコードは、ジョブを実行する必要があります。

class C(): 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     self.idx = 0 
     return self 
    def __next__(self): 
     self.idx += 1 
     if self.idx > len(self.stuff): 
      raise StopIteration 
     else: 
      return self.stuff[self.idx - 1] 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

しかし、Y-ループを終えた後、X-ループはそれが反復可能で最初の項目のみを使用していますにもかかわらず、あまりにも、行われているようです。多くの検索後

A A True 
A B False 
A C False 
A D False 

、私は最終的にitertools.teeが私に同じデータ上の2つの独立したイテレータできるようになることを期待して、次のコードを試してみました:

import itertools 
thing = C() 
thing_one, thing_two = itertools.tee(thing) 
for x in thing_one: 
    for y in thing_two: 
     print(x, y, x==y) 

をしかし、私は以前と同じ出力を得ました。

これが表す現実世界のオブジェクトは、さまざまな数のファイルとサブディレクトリを持つディレクトリとファイル構造のモデルであり、ツリーのさまざまな深さにあります。これは、この例のように、何千ものメンバーへのネストされたリンクを持っており、一度正しく繰り返します。しかし、それは、比較のために必要なときにオンザフライで多くの内部オブジェクト内で高価な処理を実行するため、反復処理を行う前に完全なコピーを作成しなければならない場合、ワークロードが倍増することになります。可能であれば、複数のイテレーターを使用して、すべてのデータを含む単一のオブジェクトを指し示したいと思います。


の回答の編集:質問のコードの重要な欠陥は、すべての答えで指摘し、独立して、複数の発信者を扱うことができない単一の内部self.idx変数です。受け入れられた答えは私の実際のクラス(この再現可能な例ではあまり単純化されていません)に最適です。もう一つの答えはここに示したリストのような単純なデータ構造のシンプルで洗練されたソリューションです。

+0

そのようなiterableを書くよう教えてくれたソースは、使用を中止してください。 – user2357112

+0

オブジェクトはインデックス可能ですか?それは '__len__'メソッドを持っていますか? –

+0

これは基本的にネストされたディレクトリとファイル構造を表しているので、複数のレベルでは、その中に単一のインデックスはありません。しかし、私はノードの総数を持っているので、簡単に__len__を書くことができます。 – mightypile

答えて

1

それは独自のイテレータであるコンテナクラスを作ることは実際には不可能です。コンテナはイテレータの状態を知るべきではなく、イテレータはコンテナの内容を知る必要はなく、対応するコンテナと「どこに」あるのかを知る必要があります。イテレータとコンテナを混在させると、異なるイテレータは正しい結果(同じ変数を読み込み、変更する)を与えない互いの状態(あなたのケースではself.idx)を共有します。

全ての組み込み型が別々のイテレータ・クラスを持っている(とでもいくつかは、逆イテレータクラスを持っている)理由です:

>>> l = [1, 2, 3] 
>>> iter(l) 
<list_iterator at 0x15e360c86d8> 
>>> reversed(l) 
<list_reverseiterator at 0x15e360a5940> 

>>> t = (1, 2, 3) 
>>> iter(t) 
<tuple_iterator at 0x15e363fb320> 

>>> s = '123' 
>>> iter(s) 
<str_iterator at 0x15e363fb438> 

だから、基本的にはあなただけ__iter__iter(self.stuff)を返し、ドロップができ完全__next__list_iteratorは、リストを反復処理する方法を知っているので:期待のよう

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return iter(self.stuff) 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

は、16行を表示します。

独自の反復子クラスを作成することを目標とする場合は、2つのクラス(または逆反復子を自分で実装する場合は3つ)が必要です。

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return C_iterator(self) 
    def __reversed__(self): 
     return C_reversed_iterator(self) 

class C_iterator: 
    def __init__(self, parent): 
     self.idx = 0 
     self.parent = parent 
    def __iter__(self): 
     return self 
    def __next__(self): 
     self.idx += 1 
     if self.idx > len(self.parent.stuff): 
      raise StopIteration 
     else: 
      return self.parent.stuff[self.idx - 1] 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

も同様です。代わりに、あなたは発電機を使用することができ、独自のイテレータを定義する

class C_reversed_iterator: 
    def __init__(self, parent): 
     self.parent = parent 
     self.idx = len(parent.stuff) + 1 
    def __iter__(self): 
     return self 
    def __next__(self): 
     self.idx -= 1 
     if self.idx <= 0: 
      raise StopIteration 
     else: 
      return self.parent.stuff[self.idx - 1] 

thing = C() 
for x in reversed(thing): 
    for y in reversed(thing): 
     print(x, y, x==y) 

完全を期すため、ここでは逆イテレータの一つの可能​​な実装があります。

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     yield from self.stuff 
    def __reversed__(self): 
     yield from self.stuff[::-1] 

または明示的に発電機能(それはそれが生成され、新しいオブジェクトだということを上記に実際に同等多分もっと明らかだ)に委譲:

def C_iterator(obj): 
    for item in obj.stuff: 
     yield item 

def C_reverse_iterator(obj): 
    for item in obj.stuff[::-1]: 
     yield item 

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return C_iterator(self) 
    def __reversed__(self): 
     return C_reverse_iterator(self) 
一つの方法は、すでに他の回答で示されました

注:__reversed__イテレータを実装する必要はありません。それは単に答えの追加的な "特徴"として意味されていました。

+0

私は、自分のイテレータを実装する必要があることはほとんど確信しています。実際のクラスでは、複数のリストを順番に繰り返す必要があるため、呼び出し側には不透明です。私はまだ学んでいますが、歩留まりのような発電機が私の場合を処理するならば、私は驚くでしょう。この答えは、私が必要とされる場所に私を得るための多くの基礎をカバーしています。乾杯! – mightypile

+1

@mightypile:複数のリストを1つずつ繰り返します。 [Hmm ...](https://docs.python.org/3/library/itertools.html#itertools.chain) – ShadowRanger

1

__iter__は完全に壊れています。すべての呼び出しで実際に新しいイテレーターを作成するのではなく、ちょうどselfの状態をリセットし、selfを返します。つまり、オブジェクトに対して一度に複数のイテレータを使用することはできません。また、オブジェクト上の別のループがアクティブな間に__iter__を呼び出すと、既存のループが妨げられます。

実際に新しいオブジェクトを作成する必要があります。これを行う最も簡単な方法は、yield構文を使用してジェネレータ関数を記述することです。ジェネレータ関数は毎回新しいイテレータオブジェクトを自動的に返します:

class C(object): 
    def __init__(self): 
     self.stuff = ['A', 'B', 'C', 'D'] 
    def __iter__(self): 
     for thing in self.stuff: 
      yield thing 
+1

一般規則: '__next__'を定義すると、' __iter__' *は恒等関数でなければなりません(* nothing *では 'return self')。そうでない場合、コードは*間違っています。そして、通常、イテレータクラスを手作業で実装する必要はないので、ここで説明するように '__iter__'をジェネレータ関数にして、' __next__'を完全に実装することは避けてください。ジェネレータ関数のアプローチはより速く(Pythonでジェネレータの状態を管理することで、効率的に行うことができます)、タイプに応じた別のイテレータクラスを定義するより簡単です。 – ShadowRanger

+0

一般的な規則のように、降伏方向が役立ちます。私は彼らが私の使用のために働くかどうかを判断する必要があります - オブジェクト内の階層の複数のレベルを反復するケース。ありがとう! – mightypile

関連する問題