How to improve performance of this code?

前端 未结 7 730
一生所求
一生所求 2020-11-21 06:54

Thanks to some help from people here, I was able to get my code for Tasmanian camels puzzle working. However, it is horribly slow (I think. I\'m not sure because this is my

相关标签:
7条回答
  • 2020-11-21 07:23

    My other answer is rather long, so I decided to list this as a separate answer. This problem is really better suited to doing a depth-first search. I made a depth-first search solution and it is much, much faster than the optimized A-star method made with the changes outlined in my other answer (which is much, much faster than the OP code). For instance, here are the results for running both my A-star and my depth-first search methods on the 17 camels per side case.

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

    Here's my depth-first method code if you are interested:

    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 讨论(0)
  • 2020-11-21 07:25

    First let me tell you how to find the problem. Then I'll tell you where it is:

    I haven't even bothered to try to figure out your code. I just ran it and took 3 random-time stack samples. I did that by typing control-C and looking at the resulting stacktrace.

    One way to look at it is: if a statement appears on X% of random stack traces, then it is on the stack for about X% of the time, so that is what it's responsible for. If you could avoid executing it, that is how much you would save.

    OK, I took 3 stack samples. Here they are:

    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)))
    

    Notice, in this case the stack samples are all identical. In other words, each one of these three lines is individually responsible for nearly all of the time. So look at them:

    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)))
    

    Clearly line 87 is not one you can avoid executing, and probably not 85 either. That leaves 80, the openlist.put call. Now, you can't tell if the problem is in the + operator, the heuristicf call, the node call, or in the put call. You could find out if you could split those out onto separate lines.

    So I hope you pick up from this a quick and easy way to find out where your performance problems are.

    0 讨论(0)
  • 2020-11-21 07:29

    Replacing

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

    with

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

    dropped the times from around 340-600 msecs to 11.4 1.891 msecs on the input [fCamel, fCamel, gap, bCamel, bCamel]. It yielded the same output.

    This obviously won't help with any algorithmic problems but as far as micro-optimizations go, it isn't bad.

    1 I had the wrong input. There was an extra fCamel that was making it run slower.

    0 讨论(0)
  • 2020-11-21 07:29

    Well, I can't really say quite where your algorithm is running astray, but I just went ahead and made my own. In the interest of doing the simplest thing that could possibly work, I used a bastardized version of Dijkstra's algorithm, where open nodes are visited in arbitrary order, without consideration of distance. This means I don't have to come up with a heuristic.

    """ 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
    
    0 讨论(0)
  • 2020-11-21 07:37

    tkerwin is correct that you should be using a set for closedlist, which speeds things up a lot, but it is still kind of slow for 4 camels on each side. The next problem is that you are allowing a lot of solutions that aren't possible because you are allowing fCamels to go backwards and bCamels to go forward. To fix this, replace the lines,

    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)
    

    with

    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)
    

    then I get solution to the 4 camels on each side problem in like .05 seconds rather than 10 seconds. I also tried 5 camels on each side and it took 0.09 seconds. I also am using a set for closedlist and heapq rather than Queue.

    Additional speed-up

    You can get an additional speed-up by using your heuristic correctly. Currently, you are using the line

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

    (or the heapq version of that) but you should change it to

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

    This doesn't factor in the number of moves that has been needed, but that is okay. With this puzzle (and the screening out of moves that move camels in the wrong direction), you don't need to worry about the number of moves it takes - either a move advances you towards the solution or it will come to a dead end. In other words, all possible solutions require the same number of moves. This one change takes the time to find the solution of the 12 camels on each side case from over 13 seconds (even using the heapq, set for closedlist, and the changes to find the neighbors above) to 0.389 seconds. That's not bad.

    By the way, a better way to find if you've found the solution is to check if the index of the first fCamel is equal to the length of the formation/2 + 1(using int division) and that the index before that is equal to the gap.

    0 讨论(0)
  • 2020-11-21 07:44

    The code below is using less than 1 second to solve this:

    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)
    
    0 讨论(0)
提交回复
热议问题