Permutations between 2 lists - python

From 2 list i would like to know an optimal way in Python to do a sort of "indexed permutation".
This is how this would look like :
input :
list2 = [3,4,5]
list1 = [0,1,2]
output
[[0,1,2], [0,1,5], [0,4,2], [3,1,2],
[3,4,5], [3,4,2], [3,1,5], [0,4,5],
]
So each element of the lists remains in the same index.

You basically want two variables: list_to_pick, which can vary in range(number_of_lists), and index_to_swap which can vary in the range(-1, len(list1)). Then, you want the product of these two ranges to decide which list to pick, and which item to swap. When index_to_swap is -1, we won't swap any items
import itertools
source = [list1, list2]
result = []
for list_to_pick, index_to_swap in itertools.product(range(len(source)), range(-1, len(source[0])):
# Make a copy so we don't mess up the original list
selected_list = source[list_to_pick].copy()
# There are only two lists, so the other list is at index abs(list_to_pick - 1)
other_list = source[abs(list_to_pick - 1)]
# We swap only if index_to_swap >= 0
if index_to_swap >= 0:
selected_list[index_to_swap] = other_list[index_to_swap]
result.append(selected_list)
Which gives:
[[0, 1, 2],
[3, 1, 2],
[0, 4, 2],
[0, 1, 5],
[3, 4, 5],
[0, 4, 5],
[3, 1, 5],
[3, 4, 2]]
The order is not the same as your required list, but all the "permutations" are there. If you want the same order as in your question, you will have to define the second argument to itertools.product as:
swap_indices = [-1] + list(range(len(source[0])-1, -2, -1))

Related

Combine n elements in first half of list with elements in other half of a list, if number of elements in a list is greater than 2

I have a problem with dealing with elements in a list. To be precise in a list of lists. For example, I have list of elements that are read from a file:
list_1 = [['void', None], ['uint8', 'f_MbistTestType_u8'], ['uint8', 'uint32', 'f_MbistTestType_u8', 'f_chip_id_u32'], ['void', None], ['void', None], ['void', None], ['void', None]]
In this case third element has more than two elements. I want to switch element 2 with element 3. So it would look like this:
list_1[2] = ['uint8', 'f_MbistTestType_u8', 'uint32', 'f_chip_id_u32']
If there would be 6 elements i.e.
list_example = ['uint8', 'uint32', 'void', 'f_chip_id_u32', 'f_MbistTestType_u8', None]
After the operation it should look like this:
list_example_sorted = ['uint8', 'f_chip_id_u32', 'uint32', 'f_MbistTestType_u8', 'void', None]
Right now I know how to get those elements in case I have only one occurrence of more than 2 elements, but don't know how to switch their places and also what to do in case I have more than one occurrence:
for elements in list_1:
print(elements)
if len(elements) > 2:
list_el = elements
print(list_el)
I tried to pop them out and append, but it won't scale well with more than 4 elements.
I tried to use swap function, but it seems that it doesn't work or I used it wrong?
Going by an input of [1, 1, 1, 2, 2, 2] with the desired output [1, 2, 1, 2, 1, 2], i.e. you want the first element of the left half followed by the first element of the right half and so forth.
To make it more obvious:
input = [1, 2, 3, 4, 5, 6]
output = [1, 4, 2, 5, 3, 6]
Define a function combine_inplace that combines the ith element of the left half with the ith element of the right half of l:
def combine_inplace(l):
mid = len(l) // 2
ptr = 0
for left, right in zip(l[:mid], l[mid:]):
l[ptr], l[ptr+1] = left, right
# Increment pointer ptr by 2 for the next combination
ptr += 2
combine_inplace mutates the passed list l
left half and right half are created using slice operator
use zip to iterate over both list
increment ptr by 2 to get to the next list indices for l
If you don't want to mutate the list itself you can instead create a new list combined that is returned by the function combine:
def combine(l):
mid = len(l) // 2
combined = []
for left, right in zip(l[:mid], l[mid:]):
combined.extend((left, right))
return combined
Does not mutate the passed list l
Initialise empty list combined to store the combined values
use zip to iterate over both list halves
Returns the list combined
This uses the same logic as combine_inplace but you keep the original list intact.
Both functions combine the elements of the left half with the right half of a given list. The only difference is that with combine you have to store the returned list in a variable to access it.
>> l = [1, 1, 1, 2, 2, 2]
>> combine_inplace(l)
>> print(l)
[1, 2, 1, 2, 1, 2]
>> input_list = [1, 2, 3, 4, 5, 6]
>> output_list = combine(input_list)
>> print(output_list)
[1, 4, 2, 5, 3, 6]
Now using either combine or combine_inplace to combine elements of lists with a length > 2 inside a list:
ll = [[1, 2], [1, 2], [1, 1, 2, 2], [1, 2], [1, 2, 3, 4, 5, 6]]
# Non-destructive way using combine to create a new list comb_ll
comb_ll = []
for el in ll:
if len(el) > 2:
el = combine(el)
comb_ll.append(el)
# Mutates the original list
for i in range(len(ll)):
if len(ll[i]) > 2:
combine_inplace(ll[i])
In both cases you'll get the same result:
>> print(comb_ll)
[[1, 2], [1, 2], [1, 2, 1, 2], [1, 2], [1, 4, 2, 5, 3, 6]]
>> print(ll)
[[1, 2], [1, 2], [1, 2, 1, 2], [1, 2], [1, 4, 2, 5, 3, 6]]

Adding M0, M1, ... Mn between each two elements in a list

I would like to loop through a list, which is called output as shown below:
output = [0, 1, 2, 3, 4, 5, 6]
But I have to exclude the first and last elements [1, 2, 3, 4, 5]
Then I would like to add between each two elements M. For the first two elements M0, the second two elements M1 and so on.
x= [1, M0, 2, M1, 3, M2, 4, M3, 5]
The expected output =
[[1, M0, 2],[2, M1, 3],[3, M2, 4],[4, M3, 5]]
And could I solve it with (while, for, and if) instead of using multiple for loops? Could you please assist me?
Below is my try:
Python code:
output = [0, 1, 2, 3, 4, 5, 6]
Total = []
for i in output:
if i != output[0] and i != output[-1]:
Total.append(i)
print(Total)
for j in (Total):
print(j)
h = []
for x in Total:
for y in (x,'M'0):
h.append(y)
print(h)
Here is a solution you can try, using zip to create pairs & enumerate over that.
output = [0, 1, 2, 3, 4, 5, 6]
output = output[1:-1] # Remove first & last digit
print(
[[i, f'M{index}', j] for index, (i, j) in enumerate(zip(output, output[1:]))]
)
[[1, 'M0', 2], [2, 'M1', 3], [3, 'M2', 4], [4, 'M3', 5]]
output = [0, 1, 2, 3, 4, 5, 6]
l = []
#remove the first and last element, you also can use pop(output.pop(0), output.pop(-1))
for i in output[1:-1]:
l.append(i)
print(l)
i = 1
count=1
while i < len(l):
adding = f'M{count}'
l.insert(i, adding )
i += 2
print(l)
There is a roundrobin() function in either the more_itertools module, or as a recipe in the docs for the itertools module. Then you just need to construct the two lists — the original without the first and last elements, and the Mn sequence.
from more_itertools import roundrobin
output = [0, 1, 2, 3, 4, 5, 6]
h = roundrobin(
output[1:-1],
('M%d' % i for i in range(len(output) - 3)),
)
print(list(h))
Note that roundrobin() returns a generator; if you need to use it more than once, or if you want to print it out, convert the result to a list. If you're just going to iterate through it or otherwise use it just once, you can leave it as a generator.

Generate all combinations of 2 lists (game playing)

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]]

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]]

Merge two lists based on condition

I am trying to merge two lists based on position of index, so sort of a proximity intersection.
A set doesn't work in this case. What i am trying to do is match index in each list then if the element is one less than that of the element in other list, only then i collect it.
An example will explain my scenario better.
Sample Input:
print merge_list([[0, 1, 3], [1, 2], [4, 1, 3, 5]],
[[0, 2, 6], [1, 4], [2, 2], [4, 1, 6]])
Sample Output:
[[0,2],[4,6]]
so on position 0 in list1 we have 1, 3 and in list2 we have 2, 6. Since 1 is one less than 2, so we collect that and move on, now 3 is less than 6 but it's not one less than i.e. not 5 so we ignore that. Next we have [1, 2][1, 4], so both index/position 1, but 2 is not one less than 4 so we ignore that. Next we have [2, 2] in list2 both index 2 doesn't match any index in first list so no comparison. Finally we have [4, 1, 3, 5] [4, 1, 6] comparison. Both index match and only 5 in list one is one less than list two so we collect six hence we collect [4,6] meaning index 4 and match etc.
I have tried to make it work, but i don't seem to make it work.
This is my code so far.
def merge_list(my_list1, my_list2):
merged_list = []
bigger_list = []
smaller_list = []
temp_outer_index = 0
temp_inner_index = 0
if(len(my_list1) > len(my_list2)):
bigger_list = my_list1
smaller_list = my_list2
elif(len(my_list2) > len(my_list1)):
bigger_list = my_list2
smaller_list = my_list1
else:
bigger_list = my_list1
smaller_list = my_list2
for i, sublist in enumerate(bigger_list):
for index1 , val in enumerate(sublist):
for k, sublist2 in enumerate(smaller_list):
for index2, val2 in enumerate(sublist2):
temp_outer_index = index1 + 1
temp_inner_index = index2 + 1
if(temp_inner_index < len(sublist2) and temp_outer_index < len(sublist)):
# print "temp_outer:%s , temp_inner:%s, sublist[temp_outer]:%s, sublist2[temp_inner_index]:%s" % (temp_outer_index, temp_inner_index, sublist[temp_outer_index], sublist2[temp_inner_index])
if(sublist2[temp_inner_index] < sublist[temp_outer_index]):
merged_list.append(sublist[temp_outer_index])
break
return merged_list
No clue what you are doing, but this should work.
First, convert the list of lists to a mapping of indices to set of digits contained in that list:
def convert_list(l):
return dict((sublist[0], set(sublist[1:])) for sublist in l)
This will make the lists a lot easier to work with:
>>> convert_list([[0, 1, 3], [1, 2], [4, 1, 3, 5]])
{0: set([1, 3]), 1: set([2]), 4: set([1, 3, 5])}
>>> convert_list([[0, 2, 6], [1, 4], [2, 2], [4, 1, 6]])
{0: set([2, 6]), 1: set([4]), 2: set([2]), 4: set([1, 6])}
Now the merge_lists function can be written as such:
def merge_lists(l1, l2):
result = []
d1 = convert_list(l1)
d2 = convert_list(l2)
for index, l2_nums in d2.items():
if index not in d1:
#no matching index
continue
l1_nums = d1[index]
sub_nums = [l2_num for l2_num in l2_nums if l2_num - 1 in l1_nums]
if sub_nums:
result.append([index] + sorted(list(sub_nums)))
return result
Works for your test case:
>>> print merge_lists([[0, 1, 3], [1, 2], [4, 1, 3, 5]],
[[0, 2, 6], [1, 4], [2, 2], [4, 1, 6]])
[[0, 2], [4, 6]]
I believe this does what you want it to do:
import itertools
def to_dict(lst):
dct = {sub[0]: sub[1:] for sub in lst}
return dct
def merge_dicts(a, b):
result = []
overlapping_keys = set.intersection(set(a.keys()), set(b.keys()))
for key in overlapping_keys:
temp = [key] # initialize sublist with index
for i, j in itertools.product(a[key], b[key]):
if i == j - 1:
temp.append(j)
if len(temp) > 1: # if the sublist has anything besides the index
result.append(temp)
return result
dict1 = to_dict([[0, 1, 3], [1, 2], [4, 1, 3, 5]])
dict2 = to_dict([[0, 2, 6], [1, 4], [2, 2], [4, 1, 6]])
result = merge_dicts(dict1, dict2)
print(result)
Result:
[[0, 2], [4, 6]]
First, we convert your lists to dicts because they're easier to work with (this separates the key out from the other values). Then, we look for the keys that exist in both dicts (in the example, this is 0, 1, 4) and look at all pairs of values between the two dicts for each key (in the example, 1,2; 1,6; 3,2; 3,6; 2,4; 1,1; 1,6; 3,1; 3,6; 5,1; 5,6). Whenever the first element of a pair is one less than the second element, we add the second element to our temp list. If the temp list ends up containing anything besides the key (i.e. is longer than 1), we add it to the result list, which we eventually return.
(It just occurred to me that this has pretty bad performance characteristics - quadratic in the length of the sublists - so you might want to use Claudiu's answer instead if your sublists are going to be long. If they're going to be short, though, I think the cost of initializing a set is large enough that my solution might be faster.)
def merge_list(a, b):
d = dict((val[0], set(val[1:])) for val in a)
result = []
for val in b:
k = val[0]
if k in d:
match = [x for x in val[1:] if x - 1 in d[k]]
if match:
result.append([k] + match)
return result
Similar to the other answers, this will first convert one of the lists to a dictionary with the first element of each inner list as the key and the remainder of the list as the value. Then we walk through the other list and if the first element exists as a key in the dictionary, we find all values that meet your criteria using the list comprehension and if there were any, add an entry to the result list which is returned at the end.

Categories