Here is my solution. As Tim Peters noted, this is a Hamiltonian path problem. So, the first step is to create a graph in some form.
Well, the zero step in this case is for generating prime numbers. I am going to use a sieve, but whatever the best test is. We need primes up to 2 * n , since this is the largest of the two numbers that can be summed.
m = 8 n = m + 1 # Just so I don't have to worry about zero indexes and random +/- 1's primelen = 2 * m prime = [True] * primelen prime[0] = prime[1] = False for i in range(4, primelen, 2): prime[i] = False for i in range(3, primelen, 2): if not prime[i]: continue for j in range(i * i, primelen, i): prime[j] = False
Ok, now we can check for primality with prime[i] . Now it's easy to make the edges of the graph. If I have the number i, then which numbers may appear next. I will also take advantage of the fact that I and j have the opposite parity.
pairs = [set(j for j in range(i%2+1, n, 2) if prime[i+j]) for i in range(n)]
So, here pairs[i] given an object whose elements are integers j such that i+j is prime.
Now we need to go through the schedule. This is indeed the place where time is required, and all further optimizations will be made here.
chains = [ ([], set(range(1, n)) ]
chains will track the actual paths as they progress. Your first result in the tuple. The second element is all unused numbers or invisible nodes. The idea is to pull one chain out of the queue, take a step down the path and return it.
while chains: chain, unused = chains.pop() if not chain: # we haven't even started, all unused are valid valid_next = unused else: # We need numbers that are both unused and paired with the last node # Using sets makes this easy valid_next = unused & pairs[chains[-1]] for num in valid_next: # Take a step to the new node and add the new path back to chains # Reminder, its important not to mutate anything here, always make new objs newchain = chain + [num] newunused = unused - set([num]) chains.append( (newchain, newunused) ) # are we done? if not newunused: print newchain chains = False
Please note that if there is no valid next step, the path will be deleted without replacement.
This is really inefficient, but works in a reasonable amount of time. The biggest performance bottleneck is walking on schedule, so the next optimization will appear and insert paths into smart places to prioritize the most likely paths. In this case, it would be useful to use collections.deque or another container for your chains.
EDIT
Here is an example of how you can realize your path priority. We will assign an account to each path and save a list of chains sorted by this account. For a simple example, I assume that paths containing "more complicated to use" nodes deserve more. That is, for each step along the path, the score will increase by n - len(valid_next) modified code will look something like this.
import bisect chains = ... chains_score = [0] while chains: chain, unused = chains.pop() score = chains_score.pop() ... for num in valid_next: newchain = chain + [num] newunused = unused - set([num]) newscore = score + n - len(valid_next) index = bisect.bisect(chains_score, newscore) chains.insert(index, (newchain, newunused)) chains_score.insert(index, newscore)
Remember that the insert is O(n) , so the overhead of adding this object can be quite large. It is worth doing some analysis on your evaluation algorithm to keep the length of the len(chains) queue manageable.