5

ID ペアのリスト ([("A","B"),("B","C"),("C","D"),...]) とシーケンスを読み取る関数を作成しました。ブランチを含む ID の最初から最後まで。

順序付けられた ID の各リストは、Alignment と呼ばれるクラスに保持されます。この関数は、再帰を使用して、ブランチがメイン リストから分岐する ID から始まる新しいアライメントを作成することにより、ブランチを処理します。

特定の入力を使用すると、Python によって設定された最大再帰制限に達する可能性があることがわかりました。sys.setrecursionlimit() を使用してこの制限を増やすことができることはわかっていますが、可能な分岐の組み合わせの数がわからないため、この戦術は避けたいと思います。

再帰関数を反復関数に変換することに関するいくつかの記事を読みましたが、再帰は関数の途中で行われ、指数関数的になる可能性があるため、この特定の関数を処理する最良の方法を決定できませんでした。

どなたかご提案いただけますか?

ありがとう、ブライアン

コードは以下に掲載されています。

def buildAlignments(alignment, alignmentList, endIDs):
    while alignment.start in endIDs:

        #If endID only has one preceding ID: add preceding ID to alignment
        if len(endIDs[alignment.start]) == 1:
            alignment.add(endIDs[alignment.start][0])

        else:

            #List to hold all branches that end at spanEnd
            branches = []

            for each in endIDs[alignment.start]:

                #New alignment for each branch
                al = Alignment(each)

                #Recursively process each new alignment
                buildAlignments(al, branches, endIDs)

                branches.append(al)
            count = len(branches)
            i = 0
            index = 0

            #Loop through branches by length
            for branch in branches:
                if i < count - 1:

                    #Create copy of original alignment and add branch to alignment
                    al = Alignment(alignment)
                    al += branch #branches[index]
                    alignmentList.append(al)
                    i += 1

                #Add single branch to existing original alignment
                else: alignment += branch #branches[index]
                index += 1

def main():
    IDs = [("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")]

    #Gather all startIDs with corresponding endIDs and vice versa
    startIDs = {}
    endIDs = {}
    for pair in IDs:
        if not pair[0] in startIDs: startIDs[pair[0]] = []
        startIDs[pair[0]].append(pair[1])
        if not pair[1] in endIDs: endIDs[pair[1]] = []
        endIDs[pair[1]].append(pair[0])

    #Create Alignment objects from any endID that does not start another pair (i.e. final ID in sequence)
    alignments = [Alignment(end) for end in endIDs if not end in startIDs]

    #Build build sequences in each original Alignment
    i = len(alignments)
    while i:
        buildAlignments(alignments[i-1], alignments, endIDs)
        i -= 1

編集: 提供された ID は、このアルゴリズムのテストに使用した小さなサンプルにすぎないことを指摘しておく必要があります。実際には、ID のシーケンスは数千に及ぶ可能性があり、多くの枝や枝から枝が離れています。

解決策: Andrew Cooke に感謝します。新しいメソッドは、コール スタック上ではるかに単純で簡単に見えるようです。私の目的により適したものにするために、彼のコードに若干の調整を加えました。以下に完成したソリューションを含めました。

from collections import defaultdict

def expand(line, have_successors, known):
    #print line
    known.append(line)
    for child in have_successors[line[-1]]:
        newline = line + [child]
        if line in known: known.remove(line)
        yield expand(newline, have_successors, known)

def trampoline(generator):
    stack = [generator]
    while stack:
        try:
            generator = stack.pop()
            child = next(generator)
            stack.append(generator)
            stack.append(child)
        except StopIteration:
            pass

def main(pairs):
    have_successors = defaultdict(lambda: set())
    links = set()
    for (start, end) in pairs:
        links.add(end)
        have_successors[start].add(end)
    known = []
    for node in set(have_successors.keys()):
        if node not in links:
            trampoline(expand([node], have_successors, known))
    for line in known:
        print line

if __name__ == '__main__':
    main([("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")])

if line in known: known.remove(line)変更の概要: 1 つの ID で複数の文字を処理するために、行変数を文字列からリストに変更した完全なシリーズのみを保持するために、最初から最後までリストを作成するためにリンクと have_successors を交換し、expand に追加しました。

更新: したがって、最初にこれらすべてに問題があった理由は、提供された ID のリスト内の循環参照に対するものであることを発見しました。循環参照が修正されたので、どちらの方法も期待どおりに機能します。- あなたのご親切に感謝します。

4

1 に答える 1

15

あなたのコードはまとまりのない混乱です。それが何をすることになっているのか、詳しくはわかりません。もっと注意深く (よりきちんと、明確に) していれば、おそらくリファクタリングも容易になるでしょう。

とにかく、これはあなたが望むようなことをするかもしれません:

from collections import defaultdict

def expand(line, links, known):
    print 'expand'
    known.append(line)
    for child in links[line[-1]]:
        newline = line + child
        yield expand(newline, links, known)

def trampoline(generator):
    stack = [generator]
    while stack:
        try:
            generator = stack.pop()
            print 'next'
            child = next(generator)
            stack.append(generator)
            stack.append(child)
        except StopIteration:
            pass

def main(pairs):
    have_successors = set()
    links = defaultdict(lambda: set())
    for (start, end) in pairs:
        have_successors.add(start)
        links[end].add(start)
    known = []
    for node in set(links.keys()):
        if node not in have_successors:
            trampoline(expand(node, links, known))
    for line in known:
        print line

if __name__ == '__main__':
    main([("L", "G"), ("A", "B"), ("B", "I"), ("B", "H"), ("B", "C"), ("F", "G"), ("D", "E"), ("D", "J"), ("E", "L"), ("C", "D"), ("E", "F"), ("J", "K")])

next(foo)私はpython2.7を使用しました-以前のバージョンでは、foo.__next__()または同様のものに置き換える必要がある場合があります。


よりクリーンなコードを書くことについて

まず、私も学者 (天文学者) として始めた独学のプログラマーなので、同情します。続ければ、多くの「教えられた」プログラマーに追いつき、追い越すことができます。思ったほど難しくない…

2 つ目は、defaultdict のような「トリック」を使用することと、これは単なる経験/練習の問題であり、「きちんと」していることには違いがあります。私はあなたが defaultdict について知っているとは思っていません。

しかし、今できることは、クリーンでシンプルなコードを書くことです:

  • 以前のバージョンのコードに関するコメントがあると思います。「最大長」について言及していますが、長さの計算は表示されません。そのため、コメントが古くなっている (この場合、なぜそこにあるのか) か、より明確にする必要があります (なぜそれらが最大長なのか?)。一般に、コメントはできるだけ少なくする必要があります。そうしないと、時代遅れになってしまうからです。しかし同時に、コードの背後にある「アイデア」が明確でない場合は、コメントを使用する必要があります。コードはそれ自体で語る必要があるため、「ここに 2 つの数値を追加しています」とは言わず、「隠された」ロジックがある場合は「ここのフラグメントは最大長でなければならないので ...」と言ってください。

  • 使用する写真には注意してください。何らかの理由で、コードは既知の端末で始まります。つまり、最初から最後まで物事を構築しています。なぜ?それは問題の奇妙な見方です。開始点ではあるが終了点ではない点から始める方が明確ではないでしょうか? 「startIDs」を使用してそれらを成長させますか?そうすれば、あなたは「前に歩いている」のです。ちょっとしたことのように思えるかもしれませんが、コードを読むのが難しくなります。

  • 仕事に適したツールを使用してください。startID を使用していないのに、なぜマップを作成しているのですか? 必要なのはセットだけでした。おそらく集合について知らなかったのでしょう。しかし、そうでなければ、それも混乱を招きます。あなたのコードを読んでいる誰かは、あなたが何らかの理由で何かをしていると期待しています。必要以上のことをすると、彼らはその理由を知りたがります。

  • 必要のないときは数えないようにしましょう。あなたはiindexを持っていますcount。それらはすべて必要ですか?これらの種類のカウンターは、ばかげた小さな論理エラーが発生する可能性があるため、バグを発生させる最も簡単な方法です。そして、それらはコードを不明確にします。はif i < count - 1:本当に「これが最後のブランチですか」と言っていますか? もしそうなら、あなたが何を考えているかが明確なif branch == branches [-1]:ので、書いたほうがずっといいでしょう。

  • 同様に、メインのアラインメントをループします。を使用するiと、物事が複雑になります。各アラインメントを処理しているので、単にfor each alignment in alignments. アライメントが変更されているためにエラーが発生する場合は、変更されていないコピーを作成します: for each alignment in list(alignments)

  • 必要がない場合は、特殊なケースを避けてください。buildAlignment では、特殊なケースの最初にテストがあります。しかし、それは本当に必要でしたか?それがなければ同じ結果が得られたでしょうか?多くの場合、コードを単純に書くと、特別なケースは必要ないことがわかります。私のコードでは、「リンク」があるかどうかをテストする必要はありません。これらすべてのケースで問題なく動作するからです。これにより、コードが少なくなり、心配することが少なくなり、バグの可能性が少なくなります。

これらすべてのことよりも、徹底的に整頓され、几帳面でなければなりません。たくさんのアイデアがありますが、半分を試してから別のアイデアにジャンプするのではなく、それらを書き留めて 1 つずつ実行します。そうしないと、理解できない混乱とコードになってしまいます。最初は時間を無駄にしているように感じますが、混乱する時間が減ったため、結果としてより速くなっていることに気づき始めます...


発電機について

[コードを少し変更して、いくつかの場所を分離しnewlineて追加printしました。]

まず、コードを実行しましたか? それはあなたが望むようなことをしていますか?それがあなたが以前持っていたものとどのようにつながっているか分かりますか? 私expandはあなたに似ていますbuildAlignment(願っています)。

実行すると(最新バージョン)、次のように表示されます。

: python2.7 recurse.py
next
expand
next
expand
next
expand
next
expand
next
expand
next
expand
next
expand
next
next
...

何が起こっているかについての手がかりを与えるかもしれません。「トリック」はyieldステートメントです-pythonコンパイラーはそれを見て、通常の関数を作成する代わりに、ジェネレーターを作成します。

ジェネレーターは非常に奇妙なものです。それは基本的にあなたの関数(この場合はexpand)であり、段階的に実行できるように「バンドル」されています。実行は によって行われ、関数は aに到達next()するたびに再び停止します。yield

trampolineこの奇妙なバンドルが渡されます。を呼び出しますnext()それが関数を開始する「魔法の」関数です。そのため、nextが呼び出されると関数が実行を開始し、 に到達して新しいバンドルyieldを返します。次に、コマンドは古いバンドルを保存し、新しいバンドルの作業を開始し、それを呼び出して開始します...など。trampoline()next()

ジェネレーターが「やるべきことを使い果たした」とき、それは を発生させStopIterationます。そのため、式がこれ以上大きくならないポイントに到達すると、 にその例外が表示されtrampoline()ます。その時点で、最後の「古い」バンドル ( に格納されているstack) に戻り、それを再度呼び出しますnext()。このバンドルは元の場所 ( の直後) から再開yieldし、おそらく で別のループを実行して、再びwhileヒットyieldする (または実行されて が発生する) まで続行しStopIterationます。

そのため、最終的に、コードは が存在しない場合と同じことを行いyieldます! 唯一の違いは、これらのバンドルを作成し、それらを返却し続けることです。これは無意味に思えます。スタックを使用しなくなったことを除いて!バンドルが返されるため、スタックは「使い果たされていません」! そのため、独自のスタック ( list stack) を管理する必要があります。そうしないと、前の呼び出しが何であったかを知る方法がありません。

わかりました、わかりました、私はあなたがこれをまったく理解するとは思っていません。はい、それは一種のクレイジーです。ここで、「python generators」をグーグルで検索する必要があります。独自のコードを書いてテストします。しかし、うまくいけば、これが道を示しています。


ああ、また、私は昨夜考えていました。スタックを使い果たしたのは、チェーンが長すぎるためではなく、実際にはループがあるためだと思います。あなたはループを考えましたか?A->B、B->C、C->A、....

于 2011-08-16T23:54:38.357 に答える