Generate all combinations of 2 lists (game playing) - python

I am trying to generate all possible combinations between 2 lists A and B in python with a few constraints. A and B alternate in picking values, A always picks first. A and B may have overlapping values. If A has already picked a value, then B cannot pick it, and vice versa.
Both lists need not be of equal lengths. If one list has no available values to pick then I stop generating combinations
Also the elements picked by each must be in increasing order, i.e. A[1] < A[2] < .... A[n] and B[1] < B[2] < .... B[n] where A[i] and B[i] is the i-th element picked by A and B respectively
Example:
A = [1, 2, 3, 4]
B = [2, 5]
Solution I need is
(1), (2), (3), (4),
(1,2), (1,5), (2,5), (3,2), (3,5), (4,2), (4,5),
(1,2,3), (1,2,4), (3,2,4), (1,5,2), (1,5,3), (1,5,4), (2,5,3), (2,5,4), (3,5,4),
(1,2,3,5), (1,2,4,5), (3,2,4,5)
(1,2,3,5,4)
I believe itertools in python can be useful for this but I havent really figured out how to implement it for this case.
As of now, this is how I am solving it:
A = [1, 2, 3, 4]
B = [2, 5]
A_set = set(A)
B_set = set(b)
#Append both sets
C = A.union(B)
for L in range(len(C), 0, -1):
for subset in itertools.combinations(C, L):
#Check if subset meets constraints and print it if it does

As noted in comments, this is probably much too specific to be easily solved using itertools, and you should use a recursive (generator) function instead. Just pick the next element from whichever list's turn it is, keeping track of the elements already selected, and recursively call the function again, swapping and shortening the lists and adding the element to the set of selected elements, until you've got the required number.
Something like this (this might be improved by adding parameters for the current index in both lists instead of actually slicing the lists for the recursive calls):
def solve(n, lst1, lst2, selected):
if n == 0:
yield []
elif lst1:
for i, x in enumerate(lst1):
if x not in selected:
selected.add(x)
for rest in solve(n-1, lst2, lst1[i+1:], selected):
yield [x] + rest
selected.remove(x)
Or a bit more condensed:
def solve(n, lst1, lst2, selected):
if n == 0:
yield []
elif lst1:
yield from ([x] + rest for i, x in enumerate(lst1) if x not in selected
for rest in solve(n-1, lst2, lst1[i+1:], selected.union({x})))
Example:
A = [1, 2, 3, 4]
B = [2, 5]
result = [res for n in range(1, len(A)+len(B)+1) for res in solve(n, A, B, set())]
Afterwards, result is:
[[1], [2], [3], [4],
[1, 2], [1, 5], [2, 5], [3, 2], [3, 5], [4, 2], [4, 5],
[1, 2, 3], [1, 2, 4], [1, 5, 2], [1, 5, 3], [1, 5, 4], [2, 5, 3], [2, 5, 4], [3, 2, 4], [3, 5, 4],
[1, 2, 3, 5], [1, 2, 4, 5], [3, 2, 4, 5],
[1, 2, 3, 5, 4]]

Related

Trying to write a combinations function in Python on my own, not working

I'm trying to solve this problem here: https://codingbat.com/prob/p252079?parent=/home/peter#norvig.com
In math, a "combination" of a set of things is a subset of the things. We define the function combinations(things, k) to be a list of all the subsets of exactly k elements of things. Conceptually, that's all there is, but there are some questions to settle: (A) how do we represent a subset? (B) What order are the elements within each subset? (C) What order to we list the subsets? Here's what we will agree to: (A) a subset will be a list. (B) The order of elements within a list will be the same as the order within 'things'. So, for example, for combinations([1, 2, 3], 2) one of the subsets will be [1, 2]; whereas [2, 1] is not a subset. (C) The order of subsets will be lexicographical or sorted order -- that is, combinations([1, 2, 3], 2) returns [ [1, 2], [1, 3], 2, 3] ] because [1, 2] < [1, 3] < [2, 3]. You might want to use the function 'sorted' to make sure the results you return are properly ordered.
combinations([1, 2, 3, 4, 5], 2) → [[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
combinations([1, 2, 3], 2) → [[1, 2], [1, 3], [2, 3]]
combinations([1, 2, 3, 4, 5, 6], 5) → [[1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 5, 6], [1, 2, 4, 5, 6], [1, 3, 4, 5, 6], [2, 3, 4, 5, 6]]
Here's my code:
def combinations(things, k):
if k == 0 or k == len(things):
return [things]
elif len(things) < k:
return
else:
finalcomb = []
subcomb1 = combinations(things[1:], k - 1)
subcomb2 = combinations(things[1:], k)
for i in range(len(combinations(things[1:], k - 1))):
firstelement = [things[0]]
firstelement += combinations(things[1:], k - 1)[i]
finalcomb.append(firstelement)
for j in range(len(combinations(things[1:], k))):
finalcomb.append(combinations(things[1:], k)[j])
return finalcomb
However, this is the output:
Haven't hit 10 reputation yet so it's a link to the error. I'm not sure what I did wrong, can anybody help me out? Thank you so much.
The problem is this. When k == 0 it shouldn't return [things]. It should return an empty array. Similar to when len(things) < k:. This is because, when k == 0, it means we that we have already found all the numbers for that specific combination.
But there's one more problem. We're returning an empty array. However, in the for loops, we're iterating over the returned array. So if the array is empty, nothing happens. So what we should really return is an empty 2D array. I won't go into too much detail about what the problem is since it's better for you to try and understand why it's not working. Try adding print statements inside and outside the for loops.
Anyway, the working code looks like this:
def combinations(things, k):
if k == len(things):
return [things[:]]
if len(things) < k or k == 0:
return [[]]
finalcomb = []
subcomb1 = combinations(things[1:], k - 1)
subcomb2 = combinations(things[1:], k)
for comb in subcomb1:
firstelement = [things[0]]
firstelement += comb
finalcomb.append(firstelement)
finalcomb += subcomb2
return finalcomb
Note a few things:
Use the variables you've already assigned (I'm assuming you forgot about them)
Lists can be concatenated using +, similar to strings. If you return within an if statement, you don't need an else for the next line since if the if statement is satisfied, it would definitely not go to the else.
You simply can try using itertools:
import itertools
output = []
for nums in itertools.combinations([1, 2, 3, 4, 5], 2):
output.append(list(nums))
print(output)
output:
[[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
For 3 nums:
import itertools
output = []
for nums in itertools.combinations([1, 2, 3, 4, 5], 3):
output.append(list(nums))
print(output)
Output:
[[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]

Python - doing maths with a nested list

If I have a nested list, e.g. x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]], how can I calculate the difference between all of them? Let's called the lists inside x - A, B, and C. I want to calculate the difference of A from B & C, then B from A & C, then C from A & B, then put them in a list diff = [].
My problem is correctly indexing the numbers and using them to do maths with corresponding elements in other lists.
This is what I have so far:
for i in range(len(x)):
diff = []
for j in range(len(x)):
if x[i]!=x[j]:
a = x[i]
b = x[j]
for h in range(len(a)):
d = a[h] - b[h]
diff.append(d)
Essentially for the difference of A to B it is ([1-2] + [2-4] + [3-6])
I would like it to return: diff = [[diff(A,B), diff(A,C)], [diff(B,A), diff(B,C)], [diff(C,A), diff(C,B)]] with the correct differences between points.
Thanks in advance!
Your solution is actually not that far off. As Aniketh mentioned, one issue is your use of x[i] != x[j]. Since x[i] and x[j] are arrays, that will actually always evaluate to false.
The reason is that python will not do a useful comparison of arrays by default. It will just check if the array reference is the same. This is obviously not what you want, you are trying to see if the array is at the same index in x. For that use i !=j.
Though there are other solutions posted here, I'll add mine below because I already wrote it. It makes use of python's list comprehensions.
def pairwise_diff(x):
diff = []
for i in range(len(x)):
A = x[i]
for j in range(len(x)):
if i != j:
B = x[j]
assert len(A) == len(B)
item_diff = [A[i] - B[i] for i in range(len(A))]
diff.append(sum(item_diff))
# Take the answers and group them into arrays of length 2
return [diff[i : i + 2] for i in range(0, len(diff), 2)]
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
print(pairwise_diff(x))
This is one of those problems where it's really helpful to know a bit of Python's standard library — especially itertools.
For example to get the pairs of lists you want to operate on, you can reach for itertools.permutations
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
list(permutations(x, r=2))
This gives the pairs of lists your want:
[([1, 2, 3], [2, 4, 6]),
([1, 2, 3], [3, 5, 7]),
([2, 4, 6], [1, 2, 3]),
([2, 4, 6], [3, 5, 7]),
([3, 5, 7], [1, 2, 3]),
([3, 5, 7], [2, 4, 6])]
Now, if you could just group those by the first of each pair...itertools.groupby does just this.
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
list(list(g) for k, g in groupby(permutations(x, r=2), key=lambda p: p[0]))
Which produces a list of lists grouped by the first:
[[([1, 2, 3], [2, 4, 6]), ([1, 2, 3], [3, 5, 7])],
[([2, 4, 6], [1, 2, 3]), ([2, 4, 6], [3, 5, 7])],
[([3, 5, 7], [1, 2, 3]), ([3, 5, 7], [2, 4, 6])]]
Putting it all together, you can make a simple function that subtracts the lists the way you want and pass each pair in:
from itertools import permutations, groupby
def sum_diff(pairs):
return [sum(p - q for p, q in zip(*pair)) for pair in pairs]
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
# call sum_diff for each group of pairs
result = [sum_diff(g) for k, g in groupby(permutations(x, r=2), key=lambda p: p[0])]
# [[-6, -9], [6, -3], [9, 3]]
This reduces the problem to just a couple lines of code and will be performant on large lists. And, since you mentioned the difficulty in keeping indices straight, notice that this uses no indices in the code other than selecting the first element for grouping.
Here is the code I believe you're looking for. I will explain it below:
def diff(a, b):
total = 0
for i in range(len(a)):
total += a[i] - b[i]
return total
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
differences = []
for i in range(len(x)):
soloDiff = []
for j in range(len(x)):
if i != j:
soloDiff.append(diff(x[i],x[j]))
differences.append(soloDiff)
print(differences)
Output:
[[-6, -9], [6, -3], [9, 3]]
First off, in your explanation of your algorithm, you are making it very clear that you should use a function to calculate the differences between two lists since you will be using it repeatedly.
Your for loops start off fine, but you should have a second list to append diff to 3 times. Also, when you are checking for repeats you need to make sure that i != j, not x[i] != x[j]
Let me know if you have any other questions!!
this is the simplest solution i can think:
import numpy as np
x = [[1, 2, 3], [2, 4, 6], [3, 5, 7]]
x = np.array(x)
vectors = ['A','B','C']
for j in range(3):
for k in range(3):
if j!=k:
print(vectors[j],'-',vectors[k],'=', x[j]-x[k])
which will return
A - B = [-1 -2 -3]
A - C = [-2 -3 -4]
B - A = [1 2 3]
B - C = [-1 -1 -1]
C - A = [2 3 4]
C - B = [1 1 1]

I'm trying to rotate an array about a given index in python

case 1:
a=[1,2,3,4,5]
index k=2
a[:k],a[k:]=a[k:],a[:k]
When I swap array elements like this. I got this output.
**OUTPUT:[3, 4, 1, 2]
case 2:
b=[1,2,3,4,5]
b[k:],b[:k]=b[:k],b[k:]
but when I swap array elements like this i got this.The only difference is the order of swapping.
OUTPUT:[3, 4, 5, 1, 2]
If we swap two variables, the order of swapping doesn't make a difference.
i.e a,b=b,a is same as b,a=a,b.
Why this is not working in the case of lists/array?
The right hand side is evaluated fully before any assignments are done. Subsequently, the assignments are performed in left to right order.
So the first case evaluates to:
a[:2], a[2:] = [3, 4, 5], [1, 2]
The first assignment replaces the first two elements [1, 2] by the three elements [3, 4, 5], so now we have a == [3, 4, 5, 3, 4, 5]. The second assignment then replaces element 2 onwards by [1, 2], so it results in a == [3, 4, 1, 2].
In the second case, we have:
b[2:], b[:2] = [1, 2], [3, 4, 5]
The first assignment replaces from element 2 onwards by [1, 2] resulting in b == [1, 2, 1, 2]. The second assignment replaces the first two elements by [3, 4, 5], giving b == [3, 4, 5, 1, 2].
You may have noticed by now that this is rather tricky to think about and sometimes works mostly by accident, so I'd recommend simplifying the whole thing to:
a[:] = a[k:] + a[:k]
If you swap two variables, there's no relation between two variables, then ok.
You can find the steps as following:
>>> a=[1,2,3,4,5]
>>> k=2
>>> # a[:k], a[k:] = a[k:], a[:k]
>>> x, y = a[k:], a[:k]
>>> a[:k] = x
>>> x, a
([3, 4, 5], [3, 4, 5, 3, 4, 5])
>>> a[k:] = y
>>> y, a
([1, 2], [3, 4, 1, 2])

Get unique elements from a 2D list

I have a 2D list which I create like so:
Z1 = [[0 for x in range(3)] for y in range(4)]
I then proceed to populate this list, such that Z1 looks like this:
[[1, 2, 3], [4, 5, 6], [2, 3, 1], [2, 5, 1]]
I need to extract the unique 1x3 elements of Z1, without regard to order:
Z2 = makeUnique(Z1) # The solution
The contents of Z2 should look like this:
[[4, 5, 6], [2, 5, 1]]
As you can see, I consider [1, 2, 3] and [2, 3, 1] to be duplicates because I don't care about the order.
Also note that single numeric values may appear more than once across elements (e.g. [2, 3, 1] and [2, 5, 1]); it's only when all three values appear together more than once (in the same or different order) that I consider them to be duplicates.
I have searched dozens of similar problems, but none of them seems to address my exact issue. I'm a complete Python beginner so I just need a push in the right direction.
I have already tried :
Z2= dict((x[0], x) for x in Z1).values()
Z2= set(i for j in Z2 for i in j)
But this does not produce the desired behaviour.
Thank you very much for your help!
Louis Vallance
If the order of the elements inside the sublists does not matter, you could use the following:
from collections import Counter
z1 = [[1, 2, 3], [4, 5, 6], [2, 3, 1], [2, 5, 1]]
temp = Counter([tuple(sorted(x)) for x in z1])
z2 = [list(k) for k, v in temp.items() if v == 1]
print(z2) # [[4, 5, 6], [1, 2, 5]]
Some remarks:
sorting makes lists [1, 2, 3] and [2, 3, 1] from the example equal so they get grouped by the Counter
casting to tuple converts the lists to something that is hashable and can therefore be used as a dictionary key.
the Counter creates a dict with the tuples created above as keys and a value equal to the number of times they appear in the original list
the final list-comprehension takes all those keys from the Counter dictionary that have a count of 1.
If the order does matter you can use the following instead:
z1 = [[1, 2, 3], [4, 5, 6], [2, 3, 1], [2, 5, 1]]
def test(sublist, list_):
for sub in list_:
if all(x in sub for x in sublist):
return False
return True
z2 = [x for i, x in enumerate(z1) if test(x, z1[:i] + z1[i+1:])]
print(z2) # [[4, 5, 6], [2, 5, 1]]

Sum concisely particular elements in a list of lists

I have a variable stacks:
stacks = [[1, 2, 3], [[4, 5, 6], [1, 2, 3]]]
From this I want to create another list of heights, where each height is the sum of the element at index 1 of each stack in stacks. In the example above, heights would be:
heights = [2, 7]
Where 2 is stacks[0][1] and 7 is stacks[1][0][1] + stacks[1][1][1]. Sorry if it was unclear before. How can I do this concisely using list comprehensions, map and / or reduce?
Assuming that stacks is exactly as you've described:
>>> stacks = [[1, 2, 3], [[4, 5, 6], [1, 2, 3]]]
>>> wrapped = (s if isinstance(s[0], list) else [s] for s in stacks)
>>> total = [sum(x[1] for x in w) for w in wrapped]
>>> total
[2, 7]
It would be more natural, IMHO, if the elements of stacks were always lists of lists:
>>> stacks = [[[1, 2, 3]], [[4, 5, 6], [1, 2, 3]]]
>>> total = [sum(x[1] for x in w) for w in stacks]
>>> total
[2, 7]

Categories