Project Euler problem 83 – how creeps can help

June 19th, 2009 at 5:10 pm

Problems 18, 67, 81, 82 and 83 in Project Euler are quite similar, but increase in level of difficulty.

For all of these except 83 I’ve used a specialized dynamic-programming solution that worked quite well. Being recursive it was a tiny bit slower than the iterative approaches others have used, but I believe recursive solutions are more elegant.

Anyway, project 83 proved too hard for my approach, because it introduces real cycles into the paths. So, this time I had to arm myself with a real path-finding algorithm. Luckily, I’ve coded a pretty generic implementation of the A* algorithm quite recently for my Creeps PyGame demo.

A*, however, requires a heuristic to do its magic, and for problem 83 there’s no real heuristic. However, since a constant heuristic is acceptable and effectively “dumbs down” A* to the original Dijkstra algorithm (the heuristic of A* must be non-decreasing, which a constant satisfies), I was able to make use of the code from the Creeps game to find a solution in 0.3 seconds, with only 30 lines of code.

Related posts:

  1. Euler 68 – solving problems the hard way, but having fun along the way
  2. Project Euler problem 66 and continued fractions
  3. Project Euler problem 12
  4. Posting Project Euler solutions in my blog
  5. Project Euler problem 104

5 Responses to “Project Euler problem 83 – how creeps can help”

  1. Jack DiederichNo Gravatar Says:

    Oy ye of little faith! A simple A* Heuristic solves this problem in a fraction of a second on my 1GHz laptop. 50 lines of python including comments and vertical whiespace.

    Some things to remember about A*:

    - the heapq module is your friend
    - If you arrive at a node with a cost higher than you’ve seen at this node before you can toss out the current info, that path was a dead end.
    - the Heuristic doesn’t need to be complicated (that’s a hint)

    Here is the skeleton of my main routine

    def main(score_function):
    ..stack = []
    ..def push(pos, cost):
    ….score = score_function(pos, cost)
    ….heapq.heappush(stack, (score, (pos, cost)))
    ..def pop():
    ….score, (pos, cost) = heapq.heappop(stack)
    ….return pos, cost

    ..while stack:
    ….cur_pos, cur_cost = pop() # lowest ’score’ entry to date
    ….# do stuff

    The reason the score function is passed in is so you can try new ones easily.

  2. Jack DiederichNo Gravatar Says:

    Ahh, nevermind. You’re saying that A* collapses to Dijkstra.

  3. Python CoderNo Gravatar Says:

    Hey i tried using A star to solve this problem and I get the correct answer but it is so AWFULLY sloww (500 secs). My program looks at all 6400 cells, what do you think I am doing wrong?
    heuristic:
    [code]
    def h(n, goal):
    ....return 11 * (abs(n.col - goal.col) + abs(n.row - goal.row))
    [/code]
    If i change the 11 to a bigger number I get the wrong answer.
    Any suggestions
    [code]
    def astar(start, goal, matrix):
    ....closedset = []
    ....openset = []
    ....start.hscore = h(start, goal, matrix)
    ....start.fscore = start.hscore + start.gscore
    ....openset.append(start)

    ....while openset:
    ........x = None # find node with lowest fscore
    ........for n in openset:
    ............if not x or n.fscore < x.fscore:
    ................x = n

    if x.row == goal.row and x.col == goal.col:
    return x.gscore

    print x.row, x.col, len(closedset), x.fscore

    openset.remove(x)
    closedset.append(x)

    neighbors = []
    if x.row != len(matrix)-1:
    neighbors.append(matrix[x.row+1][x.col])

    if x.row != 0:
    neighbors.append(matrix[x.row-1][x.col])

    if x.col != len(matrix[0])-1:
    neighbors.append(matrix[x.row][x.col+1])

    if x.col != 0:
    neighbors.append(matrix[x.row][x.col-1])

    ........for y in neighbors:
    ............if y in closedset:
    ................continue

    gscore = x.gscore + y.gscore
    better = False

    if not y in openset:
    openset.append(y)
    y.hscore = h(y, goal, matrix)
    better = True

    elif gscore < y.gscore:
    better = True

    if better:
    y.parent = x
    y.gscore = gscore
    y.fscore = y.gscore + y.hscore

    return None
    [/code]
    Sorry for the long code =/

  4. elibenNo Gravatar Says:

    @Python Coder,

    Perhaps you could use a real ’set’ or a dict type for the open and closed sets instead of lists? Removing from a list is O(n).

    You can download and compare my A* code from the Creeps game link I provided – it’s quite short and simple, and well commented.

  5. Python CoderNo Gravatar Says:

    thanks, after changing closedset and openset to sets it runs in a little over 2 seconds, wow!
    such a difference it makes

Leave a Reply

To post code with preserved formatting, enclose it in `backticks` (even multiple lines)