web-dev-qa-db-ja.com

このコードのパフォーマンスを向上させる方法は?

ここの人々の助けのおかげで、タスマニアのラクダパズルのコードを機能させることができました。しかし、それはひどく遅いです(私は思います。これはPythonでの私の最初のプログラムなのでわかりません)。コードの下部で実行されている例は、私のマシンで解決するのに長い時間がかかります。

dumrat@dumrat:~/programming/python$ time python camels.py
[['F', 'F', 'F', 'G', 'B', 'B', 'B'], ['F', 'F', 'G', 'F', 'B', 'B', 'B'],
 ['F', 'F', 'B', 'F', 'G', 'B', 'B'], ['F', 'F', 'B', 'F', 'B', 'G', 'B'],
 ['F', 'F', 'B', 'G', 'B', 'F', 'B'], ['F', 'G', 'B', 'F', 'B', 'F', 'B'],
 ['G', 'F', 'B', 'F', 'B', 'F', 'B'], ['B', 'F', 'G', 'F', 'B', 'F', 'B'],
 ['B', 'F', 'B', 'F', 'G', 'F', 'B'], ['B', 'F', 'B', 'F', 'B', 'F', 'G'],
 ['B', 'F', 'B', 'F', 'B', 'G', 'F'], ['B', 'F', 'B', 'G', 'B', 'F', 'F'],
 ['B', 'G', 'B', 'F', 'B', 'F', 'F'], ['B', 'B', 'G', 'F', 'B', 'F', 'F'],
 ['B', 'B', 'B', 'F', 'G', 'F', 'F']]

real    0m20.883s
user    0m20.549s
sys    0m0.020s

コードは次のとおりです。

import Queue

fCamel = 'F'
bCamel = 'B'
gap = 'G'

def solution(formation):
    return len([i for i in formation[formation.index(fCamel) + 1:]
                if i == bCamel]) == 0

def heuristic(formation):
    fCamels, score = 0, 0
    for i in formation:
        if i == fCamel:
            fCamels += 1;
        Elif i == bCamel:
            score += fCamels;
        else:
            pass
    return score

def getneighbors (formation):
    igap = formation.index(gap)
    res = []
    # AB_CD --> A_BCD | ABC_D | B_ACD | ABD_C
    def genn(i,j):
        temp = list(formation)
        temp[i], temp[j] = temp[j], temp[i]
        res.append(temp)

    if(igap > 0):
        genn(igap, igap-1)
    if(igap > 1):
        genn(igap, igap-2)
    if igap < len(formation) - 1:
        genn(igap, igap+1)
    if igap < len(formation) - 2:
        genn(igap, igap+2)

    return res

class node:
    def __init__(self, a, g, p):
        self.arrangement = a
        self.g = g
        self.parent = p

def astar (formation, heuristicf, solutionf, genneighbors):

    openlist = Queue.PriorityQueue()
    openlist.put((heuristicf(formation), node(formation, 0, None)))
    closedlist = []

    while 1:
        try:
            f, current = openlist.get()
        except IndexError:
            current = None

        if current is None:
            print "No solution found"
            return None;

        if solutionf(current.arrangement):
            path = []
            cp = current
            while cp != None:
                path.append(cp.arrangement)
                cp = cp.parent
            path.reverse()
            return path

        #arr = current.arrangement
        closedlist.append(current)
        neighbors = genneighbors(current.arrangement)

        for neighbor in neighbors:
            if neighbor in closedlist:
                pass
            else:
                openlist.put((current.g + heuristicf(neighbor),
                             node(neighbor, current.g + 1, current)))

        #sorted(openlist, cmp = lambda x, y : x.f > y.f)

def solve(formation):
    return astar(formation, heuristic, solution, getneighbors)

print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
#print solve([fCamel, fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel, bCamel])

それはそれぞれ3頭のラクダのためだけです。少なくとも4回はやりたかった。そのテストケースはまだ実行中です(約5分経ちました:()。終了したら更新します。

このコードを改善するにはどうすればよいですか? (ほとんどの場合、パフォーマンスに関してですが、他の提案も歓迎します)。

38
nakiya

私もこれにつまずいたことがあります。ここでのボトルネックは実際にはif neighbor in closedlist

inステートメントは非常に使いやすく、線形検索であることを忘れてしまいます。リストで線形検索を行うと、すぐに合計される可能性があります。できることは、closedlistをsetオブジェクトに変換することです。これにより、アイテムのハッシュが保持されるため、in演算子はリストよりもはるかに効率的です。ただし、リストはハッシュ可能なアイテムではないため、構成をリストではなくタプルに変更する必要があります。

closedlistの順序がアルゴリズムにとって重要である場合は、in演算子のセットを使用して、結果の並列リストを保持できます。

Aaronasterlingのnamedtupleトリックを含むこれの簡単な実装を試しましたが、最初の例では0.2秒、2番目の例では2.1秒で実行されましたが、2番目に長い例の結果を検証しようとはしていません。

38
tkerwin

まず、問題を見つける方法を説明します。それから私はそれがどこにあるかをあなたに話します:

私はあなたのコードを理解しようとさえ気にしませんでした。私はそれを実行し、3つのランダムタイムスタックサンプルを取得しました。これを行うには、control-Cと入力し、結果のスタックトレースを確認します。

それを見る1つの方法は、ステートメントがランダムスタックトレースのX%に表示される場合、そのステートメントは約X%の時間スタックにあるため、それが原因です。あなたがそれを実行することを避けることができれば、それはあなたがどれだけ節約するでしょう。

OK、3つのスタックサンプルを取りました。はい、どうぞ:

File "camels.py", line 87, in <module>
  print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
  return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
  openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))

File "camels.py", line 87, in <module>
  print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
  return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
  openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))

File "camels.py", line 87, in <module>
  print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
  return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
  openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))

この場合、スタックサンプルはすべて同一であることに注意してください。言い換えれば、これらの3つの行のそれぞれは、ほぼすべての時間に個別に責任があります。だからそれらを見てください:

line        87: print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
line solve: 85: return astar(formation, heuristic, solution, getneighbors)
line astar: 80: openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))

明らかに、87行目は実行を回避できる行ではなく、おそらく85行目でもありません。これで80、openlist.put呼び出しが残ります。現在、問題が+演算子、heuristicf呼び出し、node呼び出し、またはput呼び出しのいずれにあるのかわかりません。それらを別々の行に分割できるかどうかを知ることができます。

ですから、これからパフォーマンスの問題がどこにあるかをすばやく簡単に見つける方法を見つけてください。

60
Mike Dunlavey

tkerwinは、クローズドリストのセットを使用する必要があることは正しいです。これにより、処理速度が大幅に向上しますが、両側に4頭のラクダがいる場合はまだ少し遅いです。次の問題は、fCamelが後方に移動し、bCamelが前方に移動することを許可しているため、不可能な多くのソリューションを許可していることです。これを修正するには、行を置き換えます。

if(igap > 0):
    genn(igap, igap-1)
if(igap > 1):
    genn(igap, igap-2)
if igap < len(formation) - 1:
    genn(igap, igap+1)
if igap < len(formation) - 2:
    genn(igap, igap+2)

if(igap > 0 and formation[igap-1] == fCamel):
    genn(igap, igap-1)
if(igap > 1 and formation[igap-2] == fCamel):
    genn(igap, igap-2)
if (igap < len(formation) - 1) and formation[igap+1] == bCamel:
    genn(igap, igap+1)
if (igap < len(formation) - 2) and formation[igap + 2] == bCamel:
    genn(igap, igap+2)

次に、両側の4頭のラクダの問題を10秒ではなく.05秒で解決します。また、両側に5頭のラクダを試しましたが、0.09秒かかりました。また、Queueではなくclosedlistとheapqのセットを使用しています。

追加のスピードアップ

ヒューリスティックを正しく使用することで、さらに高速化できます。現在、あなたはラインを使用しています

openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))

(またはそのheapqバージョン)ただし、次のように変更する必要があります

openlist.put((heuristicf(neighbor), node(neighbor, current.g + 1, current)))

これは必要な移動の数を考慮していませんが、それは問題ありません。このパズル(およびラクダを間違った方向に動かす動きのスクリーニング)を使用すると、必要な動きの数を心配する必要はありません-動きが解決に向かって進むか、行き止まりになります。言い換えれば、すべての可能な解決策は同じ数の動きを必要とします。この1つの変更は、各サイドケースで12頭のラクダの解を見つけるのに、13秒以上(クローズドリストに設定されたheapqを使用し、上記のネイバーを見つけるための変更を使用した場合でも)から0.389秒に時間がかかります。悪くない。

ちなみに、解決策が見つかったかどうかを確認するより良い方法は、最初のfCamelのインデックスがフォーメーションの長さ/ 2 + 1(int除算を使用)に等しいかどうか、およびその前のインデックスがギャップに等しい。

9
Justin Peel

交換

class node:
    def __init__(self, a, g, p):
        self.arrangement = a
        self.g = g
        self.parent = p

node = collections.namedtuple('node', 'arrangement, g, parent')

時間を約340〜600ミリ秒から 11.4 1.891 入力のミリ秒[fCamel, fCamel, gap, bCamel, bCamel]。同じ出力が得られました。

これは明らかにアルゴリズムの問​​題には役立ちませんが、マイクロ最適化に関する限り、悪くはありません。

1入力が間違っていました。余分なfCamelがあり、実行速度が低下していました。

4
aaronasterling

以下のコードは、これを解決するために1秒未満を使用しています。

from itertools import permutations

GAP='G'
LEFT='F'
RIGHT='B'
BEGIN=('F','F','F','F','G','B','B','B','B')
END=('B','B','B','B','G','F','F','F','F')
LENGTH=len(BEGIN)

ALL=set(permutations(BEGIN))

def NextMove(seq):
    g=seq.index(GAP)
    ret = set()
    def swap(n):
        return seq[:n]+seq[n:n+2][::-1]+seq[n+2:]
    if g>0 and seq[g-1]==LEFT:
        ret.add(swap(g-1))
    if g<LENGTH-1 and seq[g+1]==RIGHT:
        ret.add(swap(g))
    if g<LENGTH-2 and seq[g+1]==LEFT and seq[g+2]==RIGHT:
        ret.add(seq[:g]+seq[g+2:g+3]+seq[g+1:g+2]+seq[g:g+1]+seq[g+3:])
    if g>1 and seq[g-1]==RIGHT and seq[g-2]==LEFT:
        ret.add(seq[:g-2]+seq[g:g+1]+seq[g-1:g]+seq[g-2:g-1]+seq[g+1:])

    return ret

AllMoves={state:NextMove(state) for state in ALL}

def Solve(now,target):
    if now==target:
        return True
    for state in AllMoves[now]:
        if Solve(state,target):
            print(now)
            return True
    return False

Solve(BEGIN,END)
3
Kabie

さて、あなたのアルゴリズムがどこで間違って実行されているかははっきりとは言えませんが、私は先に進んで自分で作成しました。おそらく機能する可能性のある最も単純なことを行うために、距離を考慮せずに開いているノードに任意の順序でアクセスする、ダイクストラのアルゴリズムのろくでなしバージョンを使用しました。これは、ヒューリスティックを考え出す必要がないことを意味します。

""" notation: a game state is a string containing angle
    brackets ('>' and '<') and blanks
 '>>> <<<'

 """

def get_moves(game):
    result = []
    lg = len(game)
    for i in range(lg):
        if game[i] == '>':
            if i < lg-1 and game[i+1] == ' ': # '> ' -> ' >'
                result.append(game[:i]+' >'+game[i+2:])
            if i < lg-2 and game[i+1] != ' ' and game[i+2] == ' ': # '>< ' -> ' <>'
                result.append(game[:i]+' '+game[i+1]+'>'+game[i+3:])
        if game[i] == '<':
            if i >= 1 and game[i-1] == ' ': # ' <' -> '< '
                result.append(game[:i-1]+'< '+game[i+1:])
            if i >= 2 and game[i-1] != ' ' and game[i-2] == ' ': # ' ><' -> '<> '
                result.append(game[:i-2]+'<'+game[i-1]+' '+game[i+1:])
    return result



def wander(start, stop):
    fringe = [start]
    paths = {}

    paths[start] = ()

    def visit(state):
      path = paths[state]
      moves = [move for move in get_moves(state) if move not in paths]
      for move in moves:
          paths[move] = paths[state] + (state,)
      fringe.extend(moves)

    while stop not in paths:
      visit(fringe.pop())

    print "still open: ", len(fringe)
    print "area covered: " , len(paths)
    return paths[stop] + (stop,)

if __name__ == "__main__":
    start = '>>>> <<<<'
    stop = '<<<< >>>>'
    print start, "   -->   ", stop
    pathway = wander(start,stop)
    print len(pathway), "moves: ", pathway

私の他の答えはかなり長いので、私はこれを別の答えとしてリストすることにしました。この問題は、深さ優先探索を実行するのに非常に適しています。私は深さ優先探索ソリューションを作成しましたが、他の回答で概説した変更を加えた最適化されたA-starメソッドよりもはるかに高速です(OPコードよりもはるかに高速です)。たとえば、サイドケースごとに17頭のラクダで、Aスターと深さ優先探索の両方の方法を実行した結果を次に示します。

A-star:  14.76 seconds
Depth-first search: 1.30 seconds

興味があれば、これが私の深さ優先メソッドコードです:

from sys import argv

fCamel = 'F'
bCamel = 'B'
gap = 'G'

def issolution(formlen):
    def solution(formation):
        if formation[formlen2] == gap:
            return formation.index(fCamel) == x
        return 0
    x = formlen/2 + 1
    formlen2 = formlen/2
    return solution

def solve(formation):
    def depthfirst(form, g):
        if checksolution(form):
            return [Tuple(form)], g + 1
        else:
            igap = form.index(gap)
            if(igap > 1 and form[igap-2] == fCamel):
                form[igap-2],form[igap] = form[igap],form[igap-2]
                res = depthfirst(form,g+1)
                form[igap-2],form[igap] = form[igap],form[igap-2]
                if res != 0:
                    return [Tuple(form)]+res[0],res[1]
            if (igap < flen - 2) and form[igap + 2] == bCamel:
                form[igap+2],form[igap] = form[igap],form[igap+2]
                res = depthfirst(form,g+1)
                form[igap+2],form[igap] = form[igap],form[igap+2]
                if res != 0:
                    return [Tuple(form)]+res[0],res[1]
            if(igap > 0 and form[igap-1] == fCamel):                
                form[igap-1],form[igap] = form[igap],form[igap-1]
                res = depthfirst(form,g+1)
                form[igap-1],form[igap] = form[igap],form[igap-1]
                if res != 0:
                    return [Tuple(form)]+res[0],res[1]               
            if (igap < flen - 1) and form[igap+1] == bCamel:
                form[igap+1],form[igap] = form[igap],form[igap+1]
                res = depthfirst(form,g+1)
                form[igap+1],form[igap] = form[igap],form[igap+1]
                if res != 0:
                    return [Tuple(form)]+res[0],res[1]                
            return 0
    flen = len(formation)
    checksolution = issolution(flen)
    res = depthfirst(list(formation), 0)
    return res

L = lambda x: Tuple([fCamel]*x + [gap] + [bCamel]*x)
print solve(L(int(argv[1])))
0
Justin Peel