简体   繁体   中英

Calculating array combinations using recursion

I wish to input an n*m array and the resulting output be an array containing the different combinations of the row elements. Here's an example to clarify (albeit an extremely simple case):

I wish to input an array of the following shape:

[[1, 2, 3]
[2, 5, 6]]

And wish to receive the following output:

[[1,2], [1,5], [1,6], [2,5], [2,6], [3,2], [3,5], [3,6]]

As you can see [2,2] is not included because of repetition.

I can write quick and dirty code containing nested for loops when the input dimensions are know a priori:

A = [[1, 2, 3], [2, 5, 6]]
m = len(A[0])    
for i in range(0, m):      
    for j in range(0, m):
            if A[0][i]!=A[1][j]:                     #check and eliminate repetition
                combined.append([A[0][i],A[1][j])
                choice_num.append([i+1, j+1])    #See (**) below

I would really like to know how to implement this as a recursive function so given some input nD array, A, one can simply call it as:

recursive_looper(A)

(**) Another feature that I would like is for the function to output the column number corresponding to the element used in the combination so we get two outputs:

element values:   [[1,2], [1,5], [1,6], [2,5], [2,6], [3,2], [3,5], [3,6]]
element position: [[1,1], [1,2], [1,3], [2,2], [2,3], [3,1], [3,2], [3,3]]

Any tips or suggestions would be greatly appreciated!!

Edit: I am open to any solution that can achieve the desired output. Recursion was simply the first thing that came to mind.

Edit 2 (Extended capabilities): This code must not be restricted to a specific list input shape but be extensible to any array of shape (n,m). I'll provide an example for where the code breaks down. The work-around was implementing n-1 conditional statements, which I would like to avoid because the array shape must be known a priori.

A = [[2, 4, 1, 11, 3], [3, 2, 1, 4, 11], [2, 3, 4, 17, 13]]

If I do not make any modifications to your indexing/filter I receive the following output for the 'filtered' list:

#[[2, 3, 2], [2, 3, 3], [2, 3, 4], [2, 3, 17], [2, 3, 13], [2, 1, 2], ..., [3, 11, 13]]

Immediately I notice that it only compared element position 0 with position 1 for 'likeness', hence why the first combination contains two 2's. I can make a modification to the Index grabber and filter loop which looks like so:

for i in range(0, len(projects_master)-2):
    indexes = [idx for idx, t in enumerate(prod) if t[i] == t[i+1] or t[i]==t[i+2] or t[i+1] == t[i+2] ]

res = []
for i in range(0, len(A)-2):
    res.append(list(filter( lambda v: v[i] != v[i+1] and v[i] != v[i+2] and v[i+1] != v[i+2], prod)))
result = [list(t) for t in res[0]]

This does give the correct output, but like I said, I needed to write out n-1 t[i] and v[i] conditions. How can this be done automatically?

EDIT 3 - FINAL Thanks a bunch to those who provided different approaches to help me achieve the same end goal. I took some insight from each and wrote something that makes sense to me and seems to function well for any input. The code which filters duplicates and removes them from the combinations is shown below:

ind_remove = []
for i in range(0, len(prod)):
    if len(prod[i]) != len(set(prod[i])):
        ind_remove.append(i)

adder=0
for i in ind_remove:
    del prod[i-adder]
    adder=adder+1   #takes into account change in indices after an element is deleted.

You can use itertools.product to generate the required combinations, which works like a cartesion product between two sets to generate the combinations.

So it the lists have been [[1, 2], [3, 4]] , the cartesian product within the sublists will be
[[1, 3], [1, 4], [2, 3], [2, 4]]

from itertools import product

a = [[1, 2, 3], [2, 5, 6]]

# Generate all possible products, *a gives you two lists
prod = list(product(*a))
#[(1, 2), (1, 5), (1, 6), (2, 2), (2, 5), (2, 6), (3, 2), (3, 5), (3, 6)]

#Get the list of duplicate indexes
indexes = [idx for idx, t in enumerate(prod) if t[0] == t[1] ]
print(indexes)
#[3]

#Remove tuples who are duplicates
res = list(filter( lambda v: v[0] != v[1], prod))
print(res)
#[(1, 2), (1, 5), (1, 6), (2, 5), (2, 6), (3, 2), (3, 5), (3, 6)]

#Convert the tuples to list
result = [list(t) for t in res]
print(result)
#[[1, 2], [1, 5], [1, 6], [2, 5], [2, 6], [3, 2], [3, 5], [3, 6]]

You can use a function that iterates over the items of the first list of the given list of lists and merge each item with the combinations from the recursive calls:

def nonrepetitive_product(lists):
    if not lists:
        yield []
        return
    first, *rest = lists
    combinations = list(nonrepetitive_product(rest))
    for item in first:
        for combination in combinations:
            if item not in combination:
                yield [item, *combination]

so that given:

l = [[1, 2, 3], [2, 5, 6]]

list(nonrepetitive_product(l)) returns:

[[1, 2], [1, 5], [1, 6], [2, 5], [2, 6], [3, 2], [3, 5], [3, 6]]

If you want the positions and values for any number of rows, you'd be better off using itertools.product and enumerate together. Filtering is a little tricky, but it can be done:

import itertools

A = [[1, 2, 3], [2, 5, 6], [7, 8, 3]]

prod = itertools.product(*map(enumerate, A))     # yields ((i,x),(j,y),(k,z),...) nested tuples
transposed = ([*zip(*pairs)] for pairs in prod)  # yields ((i,j,k,...), (x,y,z,...)) 2-tuples
filtered = [(ijk, xyz) for ijk, xyz in transposed if len(xyz) == len(set(xyz))] # filter dupes

indexes, values = zip(*filtered) # you might find `filtered` more useful than separate lists

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM