Search code examples
pythoncombinations

Finding all combinations of two sets with k elements being exchanged


Lets say I have two list ['a', 'b', 'c', 'd'] and ['x', 'y', 'z', 'w']. I want to create a set of two other list where k elements are being exchanged between the two lists:

Example for a single element:

['x', 'b', 'c', 'd'] ['a', 'y', 'z', 'w']
['a', 'x', 'c', 'd'] ['b', 'y', 'z', 'w']
['a', 'b', 'x', 'd'] ['c', 'y', 'z', 'w']
['a', 'b', 'c', 'x'] ['d', 'y', 'z', 'w']
['y', 'b', 'c', 'd'] ['x', 'a', 'z', 'w']
['a', 'y', 'c', 'd'] ['x', 'b', 'z', 'w']
['a', 'b', 'y', 'd'] ['x', 'c', 'z', 'w']
['a', 'b', 'c', 'y'] ['x', 'd', 'z', 'w']
['z', 'b', 'c', 'd'] ['x', 'y', 'a', 'w']
['a', 'z', 'c', 'd'] ['x', 'y', 'b', 'w']
['a', 'b', 'z', 'd'] ['x', 'y', 'c', 'w']
['a', 'b', 'c', 'z'] ['x', 'y', 'd', 'w']
['w', 'b', 'c', 'd'] ['x', 'y', 'z', 'a']
['a', 'w', 'c', 'd'] ['x', 'y', 'z', 'b']
['a', 'b', 'w', 'd'] ['x', 'y', 'z', 'c']
['a', 'b', 'c', 'w'] ['x', 'y', 'z', 'd']

Is there an efficient way to generate this kind of lists?

The only thing I can think of is swapping elements in a for loop:

list_0 = ['a', 'b', 'c', 'd']
list_1 = ['x', 'y', 'z', 'w']

for j in range(len(list_0)):
    for i in range(len(list_1)):
        list_0[i], list_1[j] = list_1[j], list_0[i]
        print(list_0, list_1)
        list_0 = ['a', 'b', 'c', 'd']
        list_1 = ['x', 'y', 'z', 'w']

This approach is very inefficient and am not sure how I could extend it to swapping k-elements.


Solution

  • I think the nested loops is as efficient as it gets, because you're basically generating a list of [source_index, target_index] pairs. Though re-creating the lists on every iteration is indeed wasteful.

    Here's a more straightforward solution for k=1, using itertools.product:

    import itertools
    
    list_0 = ['a', 'b', 'c', 'd']
    list_1 = ['x', 'y', 'z', 'w']
    
    indexes = range(len(list_0))
    
    for j, i in itertools.product(indexes, repeat=2):
        # Swap items.
        list_0[i], list_1[j] = list_1[j], list_0[i]
        print(list_0, list_1)
        # Swap back.
        list_0[i], list_1[j] = list_1[j], list_0[i]
    
        # Alternative without swapping, by creating new lists.
        # new_list_0 = [value if index != i else list_1[j] for index, value in enumerate(list_0)]
        # new_list_1 = [value if index != j else list_0[i] for index, value in enumerate(list_1)]
        # print(new_list_0, new_list_1)
    
    # ['x', 'b', 'c', 'd'] ['a', 'y', 'z', 'w']
    # ['a', 'x', 'c', 'd'] ['b', 'y', 'z', 'w']
    # ['a', 'b', 'x', 'd'] ['c', 'y', 'z', 'w']
    # ['a', 'b', 'c', 'x'] ['d', 'y', 'z', 'w']
    # ['y', 'b', 'c', 'd'] ['x', 'a', 'z', 'w']
    # ['a', 'y', 'c', 'd'] ['x', 'b', 'z', 'w']
    # ['a', 'b', 'y', 'd'] ['x', 'c', 'z', 'w']
    # ['a', 'b', 'c', 'y'] ['x', 'd', 'z', 'w']
    # ['z', 'b', 'c', 'd'] ['x', 'y', 'a', 'w']
    # ['a', 'z', 'c', 'd'] ['x', 'y', 'b', 'w']
    # ['a', 'b', 'z', 'd'] ['x', 'y', 'c', 'w']
    # ['a', 'b', 'c', 'z'] ['x', 'y', 'd', 'w']
    # ['w', 'b', 'c', 'd'] ['x', 'y', 'z', 'a']
    # ['a', 'w', 'c', 'd'] ['x', 'y', 'z', 'b']
    # ['a', 'b', 'w', 'd'] ['x', 'y', 'z', 'c']
    # ['a', 'b', 'c', 'w'] ['x', 'y', 'z', 'd']
    

    And here's a general solution for any k:

    import itertools
    
    list_0 = ['a', 'b', 'c', 'd']
    list_1 = ['x', 'y', 'z', 'w']
    k = 2 # Number of elements to swap.
    
    indexes = range(len(list_0))
    
    for source_indexes in itertools.permutations(indexes, k):
        for target_indexes in itertools.combinations(indexes, k):
            # Swap elements.
            for target_index, source_index in zip(source_indexes, target_indexes):
                list_0[source_index], list_1[target_index] = list_1[target_index], list_0[source_index]
    
            print(list_0, list_1)
    
            # Swap back.
            for target_index, source_index in zip(source_indexes, target_indexes):
                list_0[source_index], list_1[target_index] = list_1[target_index], list_0[source_index]
    
    # ['x', 'y', 'c', 'd'] ['a', 'b', 'z', 'w']
    # ['x', 'b', 'y', 'd'] ['a', 'c', 'z', 'w']
    # ['x', 'b', 'c', 'y'] ['a', 'd', 'z', 'w']
    # ['a', 'x', 'y', 'd'] ['b', 'c', 'z', 'w']
    # ['a', 'x', 'c', 'y'] ['b', 'd', 'z', 'w']
    # ['a', 'b', 'x', 'y'] ['c', 'd', 'z', 'w']
    # ['x', 'z', 'c', 'd'] ['a', 'y', 'b', 'w']
    # ['x', 'b', 'z', 'd'] ['a', 'y', 'c', 'w']
    # ['x', 'b', 'c', 'z'] ['a', 'y', 'd', 'w']
    # ['a', 'x', 'z', 'd'] ['b', 'y', 'c', 'w']
    # ['a', 'x', 'c', 'z'] ['b', 'y', 'd', 'w']
    # ['a', 'b', 'x', 'z'] ['c', 'y', 'd', 'w']
    # ['x', 'w', 'c', 'd'] ['a', 'y', 'z', 'b']
    # ['x', 'b', 'w', 'd'] ['a', 'y', 'z', 'c']
    # ['x', 'b', 'c', 'w'] ['a', 'y', 'z', 'd']
    # ['a', 'x', 'w', 'd'] ['b', 'y', 'z', 'c']
    # ['a', 'x', 'c', 'w'] ['b', 'y', 'z', 'd']
    # ['a', 'b', 'x', 'w'] ['c', 'y', 'z', 'd']
    # ['y', 'x', 'c', 'd'] ['b', 'a', 'z', 'w']
    # ...