Code is taking too much time

后端 未结 4 1283
死守一世寂寞
死守一世寂寞 2020-12-17 07:02

I wrote code to arrange numbers after taking user input. The ordering requires that the sum of adjacent numbers is prime. Up until 10 as an input code is working fine. If I

4条回答
  •  心在旅途
    2020-12-17 07:41

    This answer is based on @Tim Peters' suggestion about Hamiltonian paths.

    There are many possible solutions. To avoid excessive memory consumption for intermediate solutions, a random path can be generated. It also allows to utilize multiple CPUs easily (each cpu generates its own paths in parallel).

    import multiprocessing as mp
    import sys
    
    def main():
        number = int(sys.argv[1])
    
        # directed graph, vertices: 1..number (including ends)
        # there is an edge between i and j if (i+j) is prime
        vertices = range(1, number+1)
        G = {} # vertex -> adjacent vertices
        is_prime = sieve_of_eratosthenes(2*number+1)
        for i in vertices:
            G[i] = []
            for j in vertices:
                if is_prime[i + j]:
                    G[i].append(j) # there is an edge from i to j in the graph
    
        # utilize multiple cpus
        q = mp.Queue()
        for _ in range(mp.cpu_count()):
            p = mp.Process(target=hamiltonian_random, args=[G, q])
            p.daemon = True # do not survive the main process
            p.start()
        print(q.get())
    
    if __name__=="__main__":
        main()
    

    where Sieve of Eratosthenes is:

    def sieve_of_eratosthenes(limit):
        is_prime = [True]*limit
        is_prime[0] = is_prime[1] = False # zero and one are not primes
        for n in range(int(limit**.5 + .5)):
            if is_prime[n]:
                for composite in range(n*n, limit, n):
                    is_prime[composite] = False
        return is_prime
    

    and:

    import random
    
    def hamiltonian_random(graph, result_queue):
        """Build random paths until Hamiltonian path is found."""
        vertices = list(graph.keys())
        while True:
            # build random path
            path = [random.choice(vertices)] # start with a random vertice
            while True: # until path can be extended with a random adjacent vertex
                neighbours = graph[path[-1]]
                random.shuffle(neighbours)
                for adjacent_vertex in neighbours:
                    if adjacent_vertex not in path:
                        path.append(adjacent_vertex)
                        break
                else: # can't extend path
                    break
    
            # check whether it is hamiltonian
            if len(path) == len(vertices):
                assert set(path) == set(vertices)
                result_queue.put(path) # found hamiltonian path
                return
    

    Example

    $ python order-adjacent-prime-sum.py 20
    

    Output

    [19, 18, 13, 10, 1, 4, 9, 14, 5, 6, 17, 2, 15, 16, 7, 12, 11, 8, 3, 20]
    

    The output is a random sequence that satisfies the conditions:

    • it is a permutation of the range from 1 to 20 (including)
    • the sum of adjacent numbers is prime

    Time performance

    It takes around 10 seconds on average to get result for n = 900 and extrapolating the time as exponential function, it should take around 20 seconds for n = 1000:

    time performance (no set solution)

    The image is generated using this code:

    import numpy as np
    figname = 'hamiltonian_random_noset-noseq-900-900'
    Ns, Ts = np.loadtxt(figname+'.xy', unpack=True)
    
    # use polyfit to fit the data
    # y = c*a**n
    # log y = log (c * a ** n)
    # log Ts = log c + Ns * log a
    coeffs = np.polyfit(Ns, np.log2(Ts), deg=1)
    poly = np.poly1d(coeffs, variable='Ns')
    
    # use curve_fit to fit the data
    from scipy.optimize import curve_fit
    def func(x, a, c):
        return c*a**x
    popt, pcov = curve_fit(func, Ns, Ts)
    aa, cc = popt
    a, c = 2**coeffs
    
    # plot it
    import matplotlib.pyplot as plt
    plt.figure()
    plt.plot(Ns, np.log2(Ts), 'ko', label='time measurements')
    plt.plot(Ns, np.polyval(poly, Ns), 'r-',
             label=r'$time = %.2g\times %.4g^N$' % (c, a))
    plt.plot(Ns, np.log2(func(Ns, *popt)), 'b-',
             label=r'$time = %.2g\times %.4g^N$' % (cc, aa))
    plt.xlabel('N')
    plt.ylabel('log2(time in seconds)')
    plt.legend(loc='upper left')
    plt.show()
    

    Fitted values:

    >>> c*a**np.array([900, 1000])
    array([ 11.37200806,  21.56029156])
    >>> func([900, 1000], *popt)
    array([ 14.1521409 ,  22.62916398])
    

提交回复
热议问题