Combination of N elements in L groups of K items in python - python

Given a list of N elements, e.g.
mylist = [0, 1, 2, 3, 4, 5, 6, 7]
I want to find all the combinations of K elements in L groups, e.g. for K=4, L=2,
it will results as
L=0 L=1
1) [0, 1, 2, 3] [4, 5, 6, 7]
2) [0, 1, 2, 4] [3, 5, 6, 7]
3) [0, 1, 2, 5] [3, 4, 6, 7]
... etc...
69) [4, 5, 6, 7] [0, 1, 2, 3]
Notice that [0, 1, 2, 3] and [0, 1, 3, 2] would be counted as the same combination for the first group.
For the case L=2, I am using the following
from itertools import combinations
N = 8
M = 4
L = N // M
combs = list(combinations(range(N), M))
allidx = list(range(N))
for c, comb in enumerate(combs):
idx1 = list(comb)
idx2 = list(set(allidx) - set(idx1))
print(c, idx1,'\t',idx2)
First, what is the mathematical definition of this type of 'combinations'?
Second, In the case L>2, is there a more efficient way to compute them than computing all the permutations and prune them after?

You could use a recursive function, getting all combinations of k elements from the list, and combining them with the combinations for the remaining elements.
import itertools
def combs(lst, k, l):
if l == 0:
yield []
else:
for c in itertools.combinations(lst, k):
rest = [x for x in lst if x not in c]
for res in combs(rest, k, l-1):
yield [c, *res]
mylist = [0, 1, 2, 3, 4, 5, 6, 7]
for res in combs(mylist, 4, 2):
print(res)
Here, the part rest = [x for x in lst if x not in c] will only work if elements in the list are unique. If there can be duplicate elements, you can instead just get combinations of indices instead, e.g. like this (rest remains the same):
for idc in itertools.combinations(range(len(lst)), k):
comb = [lst[i] for i in idc]
idc_set = set(idc)
rest = [x for i, x in enumerate(lst) if i not in idc_set]
for res in combs(rest, k, l-1):
yield [comb, *res]
(Also, this assumes that lst has at least l*k elements.)

Here is approach that generates all unique partitions of size k from the set of length n.
Number of such partitions is (p is number of parts, equal to your L):
NPK(n,k) = n! / ((k!)^p * p!)
and grows fast (280 for n=9,k=3).
Algorithm recursively distributes items over parts. To avoid repeated generation of the same partition (like 01 23 45 and 01 45 23), we should restrict places for leading (the smallest) element of every group.
Here I used lastfilled parameter for index of the rightmost filled part, so item 0 always belongs to the 0-th part, item 1 might fall into parts 0 or 1 but not into part 2 and so on. Having intermediate result 01 __ __ we can make only 01 2_ __ at the next level, not 01 __ 2_.
def genp(l, parts, cnts, n, k, p, m, lastfilled):
if m == n:
l.append(parts[:])
return
for i in range(min(p, lastfilled + 2)):
if cnts[i] < k:
parts[i][cnts[i]] = m
cnts[i] += 1
genp(l, parts, cnts, n, k, p, m+1, max(i, lastfilled))
cnts[i] -= 1
def genpartssizek(n, k):
l = []
p = n // k
parts = [[0]*k for _ in range(p)]
cnts = [0]*p
genp(l, parts, cnts, n, k, p, 0, -1)
return l
print(genpartssizek(6,2))
[[[0, 1], [2, 3], [4, 5]],
[[0, 1], [2, 4], [3, 5]],
[[0, 1], [2, 5], [3, 4]],
[[0, 2], [1, 3], [4, 5]],
[[0, 2], [1, 4], [3, 5]],
[[0, 2], [1, 5], [3, 4]],
[[0, 3], [1, 2], [4, 5]],
[[0, 4], [1, 2], [3, 5]],
[[0, 5], [1, 2], [3, 4]],
[[0, 3], [1, 4], [2, 5]],
[[0, 3], [1, 5], [2, 4]],
[[0, 4], [1, 3], [2, 5]],
[[0, 5], [1, 3], [2, 4]],
[[0, 4], [1, 5], [2, 3]],
[[0, 5], [1, 4], [2, 3]]]

Related

Create a matrix in Python with sequences increasing

I am working on a project and I need to be able to create a function that takes in a value (n) and returns a matrix with increasing values.
ex: Given x = 2, return = [[1],[1, 2]]
Given x = 5, return = [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4],[1, 2, 3, 4, 5]]
Below is what I have thus far:
def matrixIncrease(n):
lst = []
for i in range(n):
lst.append([])
for j in range(0, n):
lst[i].append(j)
return lst
print(matrixIncrease(3))
This will just return
[[0, 1, 2], [0, 1, 2], [0, 1, 2]]
So it looks like the correct amount of inner list are being created. But, the values inside the list are not increasing correctly. Any suggestions?
Maybe you could try this List Comprehension way:
It's quite self-explnatory in the code, just play/experiment with different i and j to see the effects of changes...
Basically the j is to iterate and increase the numbers in each loop.
def create_sub(n):
''' create n sub_lists: each list has increasing items:
[1], [1, 2], [1, 2, 3], .... [1, ... n] '''
sub_matrix = [[j +1 for j in range(0, i)] # j - for col values
for i in range(1, n + 1)] # i - control rows
return sub_matrix
>>>create_sub(5)
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
>>>create_sub(8)
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8]]
You could try this:
all_ = []
def matrixIncrease(n):
if n == 1:
all_.append([1])
return[1]
j=[]
for i in range(1,n+1):
j.append(i)
matrixIncrease(n-1)
all_.append(j)
matrixIncrease(7)
print(all_)

Iterating through lists of lists in Python while keeping data structure

How can I iterate through lists of lists while keeping a lists data structure?
Here is the case:
w = [3, 3]
z = [[1, 1, 1], [2, 2]]
lst = []
for x, y in zip(w, z):
for u in y:
lst.append(u+x)
print (lst)
What I get is: [4, 4, 4, 5, 5]
What I want is: [[4, 4, 4], [5, 5]]
I need to have the original list data structure.
Based on the information available on the question, I think you should traverse both lists with an O(nxm) time complexity approximately adding the corresponding values between lists in order to preserve original data structure:
w = [3, 3]
z = [[1, 1, 1], [2, 2]]
lst = []
for i in z:
for j in w:
lst.append([a+j for a in i])
print (lst)
Output:
[[4, 4, 4], [4, 4, 4], [5, 5], [5, 5]]
Also, if you want it in one line, you can extend the use of list comprehension:
w = [3, 3]
z = [[1, 1, 1], [2, 2]]
lst = []
lst = [[a+j for a in i] for j in w for i in z]
print (lst)
Output: (In this case the order is changed, you can invert the execution order of the for loops to get the correct one if needed)
[[4, 4, 4], [5, 5], [4, 4, 4], [5, 5]]
Try this.
w = [3, 3]
z = [[1, 1, 1], [2, 2]]
lst = []
for x, y in zip(w, z):
for _ in range(len(z)):
lst.append([u+x for u in y])
print(lst)
OUTPUT: [[4, 4, 4], [4, 4, 4], [5, 5], [5, 5]]
As a one-liner:
import itertools
w = [3, 3]
z = [[1, 1, 1], [2, 2]]
lst = [[x + val for val in vals] for x, vals in itertools.product(w, z)]
print (lst)
Output:
[[4, 4, 4], [5, 5], [4, 4, 4], [5, 5]]

recursion on each element of array

I have an integer S and a collection A of integers. I need to find a set of integers from the collection where the sum of those integers is equal to S. It could be 1 integer or 50 - it doesn't matter.
I'm trying to do this like this:
I have an array res and an array grp
res starts with [0], grp is the initially given collection, and S is the sum we're trying to find, S is global
my function takes (res, grp)
I want to do this : (and example)
-
and stop when the sum of the res elements is equal to S
but I suck with recursion and I have no idea what I should be doing
this is my code
S = 7
grp = [0,5,6,4,3]
def sumFinder(res,grp):
if grp == []:
return grp #in case no pair was found [] is returned
if sum(res) == S:
return res
for i in range (0,len(grp)):
print(res)
print(grp[i])
res += [grp[i]]
newgrp = grp[:i]
newgrp += grp[i+1:]
return sumFinder(res,newgrp)
print(sumFinder([0],grp))
UPDATE:
thank you everyone for you answers
thank you englealuze for giving me a beter idea about approaching the problem thanks o you i got to this:
1 - this is for finding the first combination and returning it (this was my goal)
grp = [1,0,1,0,1,2,6,2,3,5,6,7,8,9,2,1,1,9,6,7,4,1,2,3,2,2]
S = 55
grps = []
def findCombination(comb,grp):
for i in range (0,len(grp)):
comb += [grp[i]]
newgrp = grp[:i]
newgrp += grp[i+1:]
if sum(comb) == S:
return comb
if newgrp not in grps:
grps.append(newgrp)
res = findCombination([],newgrp)
if res != None:
return res
print(findCombination([],grp))
2- this is for finding all the combinations: (this is the problem englealuze talked about, but i didn't understand his method that well even though it seems better)
grp = [1,0,1,1,9,6,7,4,1,2,3,2,2]
S = 12
grps = []
combinations = []
def findAllCombinations(comb,grp):
global combinations
for i in range (0,len(grp)):
comb += [grp[i]]
newgrp = grp
newgrp = grp[:i]
newgrp += grp[i+1:]
if sum(comb) == S and tuple(comb) not in combinations:
combinations.append(tuple(comb))
if newgrp not in grps:
grps.append(newgrp)
findAllCombinations([],newgrp)
findAllCombinations([],grp)
print(combinations)
my only problem now is that when S > 50 (in the first one), it takes longer to find the answer
what advice could you guys give me to improve both algorithms?
In stead of just providing code, I will show you how to think about this problem and how to tackle this type of problems in a general sense.
First, let us rephrase your question. In fact what you want is for a given set of numbers, find the combinations within the set which fulfill certain condition. So, you can decompose your question into 2 distinct steps.
Find all combinations of your set
Filter out the combinations that fulfill certain conditions
Let us think about how to solve the first task recursively. Remember, if a problem can be solved in a recursive way, it generally means there are some recursive patterns within your data and it usually can be solved in a very simple and clear way. If you end up with a messy recursive solution, it pretty much means you are in a wrong direction.
Let us see the pattern of your data first. If you have a very small set (1, 2), then the combinations out of this set are
1
2
1, 2
Let us increase one member to the set, (1, 2, 3). For this bigger set, all combiantions are
1 | 1, 3
2 | 2, 3
1, 2 | 1, 2, 3
| 3
Let us look at even bigger set (1, 2, 3, 4). The possible combinations are
1 1, 3 | 1, 3, 4
2 2, 3 | 2, 3, 4
1, 2 1, 2, 3 | 1, 2, 3, 4
3 | 3, 4
| 4
Now you see something interesting, the combinations of one bigger set is the combinations of the smaller set plus the additional element appending to every previous combination and plus the additional element itself.
Assume you already got the solution of all combinations of set with certain size, the solution of a bigger set can be derived from this solution. This naturally forms a recursion. You can translate this plain English directly to a recursive code as below
# assume you have got all combinations of a smaller set -> combinations(smaller(L))
# the solution of your bigger set can be directly derived from it with known new element
def combinations(L):
if L == []:
return []
else:
return next_solution(combinations(smaller(L)), newitem(L))
Notice how we decompose out task of solving a larger problem to solving smaller problems. You need the helper functions as below
# in your case a smaller set is just the new set drop one element
def smaller(L):
return L[1:]
# the new element would be the first element of new set
define newitem(L):
return L[0]
# to derive the new solution from previous combinations, you need three parts
# 1. all the previous combinations -> L
# 2. new item appending to each previous combination -> [i + [newelement] for i in L]
# 3. the new item itself [[newelement]]
def next_solution(L, newelement):
return L + [i + [newelement] for i in L] + [[newelement]]
Now we know how to get all combinations out of a set.
Then to filter them, you cannot just insert the filter directly into your recursive steps, since we rely on our previous solution to build up the result list recursively. The simple way is to filter the list while we obtain the full result of all combinations.
You will end up with a solution as this.
def smaller(L):
return L[1:]
def newitem(L):
return L[0]
def next_solution(L, newelement):
return L + [i + [newelement] for i in L] + [[newelement]]
def filtersum(L, N, f=sum):
return list(filter(lambda x: f(x)==N, L))
def combinations(L):
if L == []:
return []
else:
return next_solution(combinations(smaller(L)), newitem(L))
def filter_combinations(L, N, f=filtersum):
return f(combinations(L), N)
print(filter_combinations([0,5,6,4,3], 7))
# -> [[3, 4], [3, 4, 0]]
You can save some computations by filter out in each recursive call the combinations with sum bigger than your defined value, such as
def combinations(L):
if L == []:
return []
else:
return next_solution(list(filter(lambda x: sum(x) <=5, combinations(smaller(L)))), newitem(L))
print(combinations([1,2,3,4]))
# -> [[4], [3], [3, 2], [2], [4, 1], [3, 1], [3, 2, 1], [2, 1], [1]]
In fact there will be different ways to do recursion, depends on the way how you decompose your problem to smaller problems. There existed some smarter ways, but the approach I showed above is a typical and general approach for solving this type of problems.
I have example of solving other problems with this way
Python: combinations of map tuples
Below codes work (remove 'directly return' in the loop, changed to conditional 'return').
But I don't think it is a good solution. You need to improve your algorithm.
PS: This codes also will only return one match instead of all.
S = 7
grp = [0,3,6,4,6]
result = []
def sumFinder(res,grp, result):
for i in range (0,len(grp)):
temp = list(res) #clone res instead of direct-reference
if grp == [] or not temp:
return grp #in case no pair was found [] is returned
if sum(temp) == S:
result.append(tuple(temp))
return temp
temp.append(grp[i])
newgrp = grp[:i] + grp[i+1:]
sumFinder(list(temp),newgrp, result)
sumFinder([0], grp, result)
print result
Test Cases:
S = 7
grp = [0,3,6,4,6]
result = [(0, 0, 3, 4), (0, 0, 4, 3), (0, 3, 0, 4), (0, 3, 4), (0, 4, 0, 3), (0, 4, 3)]
[Finished in 0.823s]
Can you let me know where did you find this problem? I love to solve this type of problem, Bdw here is my approach:
a=[[[0],[0,5,6,4,3]]]
s=7
def recursive_approach(array_a):
print(array_a)
sub=[]
for mm in array_a:
array_1 = mm[0]
array_2 = mm[1]
if sum(array_2)==s:
return "result is",array_2
else:
track = []
for i in range(len(array_2)):
c = array_2[:]
del c[i]
track.append([array_1 + [array_2[i]], c])
sub.append(track)
print(sub)
return recursive_approach(sub[0])
print(recursive_approach(a))
output:
[[[0], [0, 5, 6, 4, 3]]]
[[[[0, 0], [5, 6, 4, 3]], [[0, 5], [0, 6, 4, 3]], [[0, 6], [0, 5, 4, 3]], [[0, 4], [0, 5, 6, 3]], [[0, 3], [0, 5, 6, 4]]]]
[[[0, 0], [5, 6, 4, 3]], [[0, 5], [0, 6, 4, 3]], [[0, 6], [0, 5, 4, 3]], [[0, 4], [0, 5, 6, 3]], [[0, 3], [0, 5, 6, 4]]]
[[[[0, 0, 5], [6, 4, 3]], [[0, 0, 6], [5, 4, 3]], [[0, 0, 4], [5, 6, 3]], [[0, 0, 3], [5, 6, 4]]], [[[0, 5, 0], [6, 4, 3]], [[0, 5, 6], [0, 4, 3]], [[0, 5, 4], [0, 6, 3]], [[0, 5, 3], [0, 6, 4]]], [[[0, 6, 0], [5, 4, 3]], [[0, 6, 5], [0, 4, 3]], [[0, 6, 4], [0, 5, 3]], [[0, 6, 3], [0, 5, 4]]], [[[0, 4, 0], [5, 6, 3]], [[0, 4, 5], [0, 6, 3]], [[0, 4, 6], [0, 5, 3]], [[0, 4, 3], [0, 5, 6]]], [[[0, 3, 0], [5, 6, 4]], [[0, 3, 5], [0, 6, 4]], [[0, 3, 6], [0, 5, 4]], [[0, 3, 4], [0, 5, 6]]]]
[[[0, 0, 5], [6, 4, 3]], [[0, 0, 6], [5, 4, 3]], [[0, 0, 4], [5, 6, 3]], [[0, 0, 3], [5, 6, 4]]]
[[[[0, 0, 5, 6], [4, 3]], [[0, 0, 5, 4], [6, 3]], [[0, 0, 5, 3], [6, 4]]], [[[0, 0, 6, 5], [4, 3]], [[0, 0, 6, 4], [5, 3]], [[0, 0, 6, 3], [5, 4]]], [[[0, 0, 4, 5], [6, 3]], [[0, 0, 4, 6], [5, 3]], [[0, 0, 4, 3], [5, 6]]], [[[0, 0, 3, 5], [6, 4]], [[0, 0, 3, 6], [5, 4]], [[0, 0, 3, 4], [5, 6]]]]
[[[0, 0, 5, 6], [4, 3]], [[0, 0, 5, 4], [6, 3]], [[0, 0, 5, 3], [6, 4]]]
('result is', [4, 3])

Categorizing a nested list based on the indices and the length of inner lists

I would like to categorize my nested list based on their respective indices and the length of the inner lists.
For example, if the initial list is as following:
a= [[1,2,3],[4,5,6],[3,4],[2,1],[5,6,7],[1,3,5]]
I would like to make two new nested lists, in each of which there is the same number of inner lists as the initial list, while the length of inner lists are the same. Furthermore, the index order of the list should stay the same as the initial list and for each non equal list in length, I would like to have a list with values as its previous list.
So, basically I would like the output to be as following:
a1=[[1,2,3],[4,5,6],[4,5,6],[4,5,6],[5,6,7],[1,3,5]]
a2=[[3,4],[3,4],[3,4],[2,1],[2,1],[2,1]]
Is there a logical approach to solve this problem?
Here an example that produces a dict with the sub-list length as the key and a list of all sub-lists that have said length:
a= [[1,2,3],[4,5,6],[3,4],[2,1],[5,6,7],[1,3,5]]
lengths = set([len(l) for l in a])
result = { n : [l for l in a if len(l) == n] for n in lengths}
print(result)
This gives:
{2: [[3, 4], [2, 1]], 3: [[1, 2, 3], [4, 5, 6], [5, 6, 7], [1, 3, 5]]}
EDIT:
If you want to name the lists, you can do that with
a1 = result[2] #[[3, 4], [2, 1]]
a2 = result[3] #[[1, 2, 3], [4, 5, 6], [5, 6, 7], [1, 3, 5]]
EDIT 2:
After understanding what the OP is asking for, here a code that produces the desired result:
a= [[1,2,3],[4,5,6],[3,4],[2,1],[5,6,7],[1,3,5]]
lengths = set([len(l) for l in a])
result = { n : [l for l in a if len(l) == n] for n in lengths}
iter_2 = iter(result[2])
current_2 = result[2][0]
iter_3 = iter(result[3])
current_3 = result[3][0]
a1 = []
a2 = []
for l in a:
if len(l) == 2:
current_2 = next(iter_2)
elif len(l) == 3:
current_3 = next(iter_3)
a1.append(current_2)
a2.append(current_3)
print(a1) #[[3, 4], [3, 4], [3, 4], [2, 1], [2, 1], [2, 1]]
print(a2) #[[1, 2, 3], [4, 5, 6], [4, 5, 6], [4, 5, 6], [5, 6, 7], [1, 3, 5]]
EDIT 3:
Another approach without iterators would be:
a= [[1,2,3],[4,5,6],[3,4],[2,1],[5,6,7],[1,3,5]]
a1=[l if len(l) == 2 else None for l in a]
a2=[l if len(l) == 3 else None for l in a]
for l in a1:
if l is not None:
curr1 = l
break
for l in a2:
if l is not None:
curr2 = l
break
for i in range(len(a)):
if a1[i] is None:
a1[i] = curr1
else:
curr1 = a1[i]
if a2[i] is None:
a2[i] = curr2
else:
curr2 = a2[i]
print(a1) #[[3, 4], [3, 4], [3, 4], [2, 1], [2, 1], [2, 1]]
print(a2) #[[1, 2, 3], [4, 5, 6], [4, 5, 6], [4, 5, 6], [5, 6, 7], [1, 3, 5]]
EDIT 4:
Apparently I'm getting a bit carried away, but here still a generalised solution that works with any kind of sub-list length:
a= [[1,2,3],[4,5,6],[3,4],[2,1],[5,6,7],[1,3,5]]
lengths = set([len(l) for l in a])
result = { n : [l if len(l) == n else None for l in a] for n in lengths}
current = {}
for n,res in result.items():
for l in res:
if l is not None:
current[n] = l
break
for i in range(len(a)):
for n,res in result.items():
if res[i] is None:
res[i] = current[n]
else:
current[n] = res[i]
print(result)
gives:
{
2: [[3, 4], [3, 4], [3, 4], [2, 1], [2, 1], [2, 1]],
3: [[1, 2, 3], [4, 5, 6], [4, 5, 6], [4, 5, 6], [5, 6, 7], [1, 3, 5]]
}

Remove a sublist from a list in which the numbers alreadys exist in another sublist

Given a list t:
[[0, 4], [0, 4, 5, 7], [1, 2, 3, 6], [1, 3], [2, 6], [5, 7]]
I want to remove sublists in t, e.g. [0,4], both numbers in which already exist in [0,4,5,7], I want to keep the larger groups and remove smaller groups. And so in the end [[0, 4, 5, 7], [1,2,3,6]] will be my final results.
I have tried the following code but don’t know what is wrong:
V=0
while V<60:
for o in t:
for p in t:
if o!= p:
for s in range(len(o)):
w=[]
if o[s] in p:
w.append(o[s])
#print w
if w==o:
t.remove(w)
V=V+1
print t
You could use sets for this:
lists = [[0, 4], [0, 4, 5, 7], [1, 2, 3, 6], [1, 3], [2, 6], [5, 7]]
sets = map(set, lists)
ret = []
for l in lists:
if not any(set(l) < s for s in sets):
ret.append(l)
print ret
Here, set(l) < s checks that the list l is a proper subset of s.
Or, if you like conciseness:
sets = map(set, lists)
ret = [l for l in lists if not any(set(l) < s for s in sets)]
print ret
l = [[0, 4], [0, 4, 5, 7], [1, 2, 3, 6], [1, 3], [2, 6], [5, 7]]
final = l[:]
for m in l:
for n in l:
if set(m).issubset(set(n)) and m != n:
final.remove(m)
break
print final
output:
[[0, 4, 5, 7], [1, 2, 3, 6]]

Categories

Resources