Create a random order of (x, y) pairs, without repeating/subsequent x's

后端 未结 9 2258
陌清茗
陌清茗 2021-02-08 12:25

Say I have a list of valid X = [1, 2, 3, 4, 5] and a list of valid Y = [1, 2, 3, 4, 5].

I need to generate all combinations of every element in

9条回答
  •  独厮守ぢ
    2021-02-08 12:53

    Here is an evolutionary algorithm approach. It first evolves a list in which the elements of X are each repeated len(Y) times and then it randomly fills in each element of Y len(X) times. The resulting orders seem fairly random:

    import random
    
    #the following fitness function measures
    #the number of times in which
    #consecutive elements in a list
    #are equal
    
    def numRepeats(x):
        n = len(x)
        if n < 2: return 0
        repeats = 0
        for i in range(n-1):
            if x[i] == x[i+1]: repeats += 1
        return repeats
    
    def mutate(xs):
        #swaps random pairs of elements
        #returns a new list
        #one of the two indices is chosen so that
        #it is in a repeated pair
        #and swapped element is different
    
        n = len(xs)
        repeats = [i for i in range(n) if (i > 0 and xs[i] == xs[i-1]) or (i < n-1 and xs[i] == xs[i+1])]
        i = random.choice(repeats)
        j = random.randint(0,n-1)
        while xs[j] == xs[i]: j = random.randint(0,n-1)
        ys = xs[:]
        ys[i], ys[j] = ys[j], ys[i]
        return ys
    
    def evolveShuffle(xs, popSize = 100, numGens = 100):
        #tries to evolve a shuffle of xs so that consecutive
        #elements are different
        #takes the best 10% of each generation and mutates each 9
        #times. Stops when a perfect solution is found
        #popsize assumed to be a multiple of 10
    
        population = []
    
        for i in range(popSize):
            deck = xs[:]
            random.shuffle(deck)
            fitness = numRepeats(deck)
            if fitness == 0: return deck
            population.append((fitness,deck))
    
        for i in range(numGens):
            population.sort(key = (lambda p: p[0]))
            newPop = []
            for i in range(popSize//10):
                fit,deck = population[i]
                newPop.append((fit,deck))
                for j in range(9):
                    newDeck = mutate(deck)
                    fitness = numRepeats(newDeck)
                    if fitness == 0: return newDeck
                    newPop.append((fitness,newDeck))
            population = newPop
        #if you get here :
        return [] #no special shuffle found
    
    #the following function takes a list x
    #with n distinct elements (n>1) and an integer k
    #and returns a random list of length nk
    #where consecutive elements are not the same
    
    def specialShuffle(x,k):
        n = len(x)
        if n == 2:
            if random.random() < 0.5:
                a,b = x
            else:
                b,a = x
            return [a,b]*k
        else:
            deck = x*k
            return evolveShuffle(deck)
    
    def randOrder(x,y):
        xs = specialShuffle(x,len(y))
        d = {}
        for i in x:
            ys = y[:]
            random.shuffle(ys)
            d[i] = iter(ys)
    
        pairs = []
        for i in xs:
            pairs.append((i,next(d[i])))
        return pairs
    

    for example:

    >>> randOrder([1,2,3,4,5],[1,2,3,4,5])
    [(1, 4), (3, 1), (4, 5), (2, 2), (4, 3), (5, 3), (2, 1), (3, 3), (1, 1), (5, 2), (1, 3), (2, 5), (1, 5), (3, 5), (5, 5), (4, 4), (2, 3), (3, 2), (5, 4), (2, 4), (4, 2), (1, 2), (5, 1), (4, 1), (3, 4)]
    

    As len(X) and len(Y) gets larger this has more difficulty finding a solution (and is designed to return the empty list in that eventuality), in which case the parameters popSize and numGens could be increased. As is, it is able to find 20x20 solutions very rapidly. It takes about a minute when X and Y are of size 100 but even then is able to find a solution (in the times that I have run it).

提交回复
热议问题