Understanding permutations with backtracking - python

I'm trying to figure out how the following backtracking solution works to generate all the permutations of integers given as a list:
def permutations(arr):
res = []
backtrack(arr, [], set(), res)
print(res)
def backtrack(arr, temp, visited, res):
if len(temp) == len(arr):
res.append(temp[:])
else:
for num in arr:
if num in visited: continue
visited.add(num)
temp.append(num)
backtrack(arr, temp, visited, res)
visited.remove(num)
temp.pop()
Executing with the following:
permutations([1, 2, 3])
The result is as expected:
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
What i don't understand is the temp.pop() call at the end of the loop.
I know that pop() discards the last element of the list, but why is it necessary here?
I'd really appreciate if someone could explain this to me.

It is necessary because on the next iteration the function will append again an element, so if you didn't pop beforehand the list would keep growing in size. Since backtrack adds results only if the length of temp equals the length of the original arr it won't add anything beyond the first permutation [1, 2, 3] (because from there on, the temp list keeps growing).
We can just remove that line (temp.pop()) and see what the results look like:
[[1, 2, 3]]
Now if we also change that results are added whenever len(temp) >= len(arr) then we'll see how temp grows in size:
[[1, 2, 3], [1, 2, 3, 3], [1, 2, 3, 3, 2], [1, 2, 3, 3, 2, 3]]
We get fewer results here because for each recursive call beyond [1, 2, 3] the temp list is immediately copied without ever reaching the for loop.

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

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

How to sort list of list by size of list and by element in list in mixed list

I have the following list
list = [1, 2, 3, [3, [1, 2]]]
the result would be:
[[[2, 1], 3], 3, 2, 1]
How to sort that list by size of list and by element?
Here's one way to recursively sort the list:
def recursive_sort(item):
if isinstance(item, list):
item[:] = sorted(item, key=recursive_sort)
return 0, -len(item)
else:
return 1, -item
lst = [1, 2, 3, [3, [1, 2], [2, 3, 6]]]
print(sorted(lst, key=recursive_sort))
# [[[6, 3, 2], [2, 1], 3], 3, 2, 1]
Caveat: This is more of an academic exercise and should never be used in production code. The state of the list during a sort (at least with Timsort in CPython) is undefined, so you shouldn't count on this to always work.

The append operation of list

class Solution:
"""
#param nums: A list of Integers.
#return: A list of permutations.
"""
def permute(self, nums):
# write your code here
result = []
if nums is None:
return result
self.permution(nums, 0, result)
return result
def permution(self, array, k, result):
if k == len(array):
print array
result.append(array)
else:
for i in xrange(k, len(array)):
array[i], array[k] = array[k], array[i]
self.permution(array, k+1, result)
array[i], array[k] = array[k], array[i]
if __name__=="__main__":
print Solution().permute([1, 2, 3])
This is my code about permutations. When I output the answer, I found the problem.
If I use print array,the output is
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 2, 1]
[3, 1, 2]
and it's right. But when I use result.append(array),the output is
[[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
I don't understand why it's not the same.
This is a simple call_by_reference issue. You are passing the array by reference not by value in each recursion. Since the array is passed by reference the changes in any recursive step applies to all sub-arrays in result because each sub-array is a reference of the same array. To get the correct result do as follows:
Replace:
self.permution(array, k+1, result)
with:
self.permution(array[:], k+1, result)
For more details on the topic see passing arguments.

Confounding recursive list append in Python

I'm trying to create a pair of functions that, given a list of "starting" numbers, will recursively add to each index position up to a defined maximum value (much in the same way that a odometer works in a car--each counter wheel increasing to 9 before resetting to 1 and carrying over onto the next wheel).
The code looks like this:
number_list = []
def counter(start, i, max_count):
if start[len(start)-1-i] < max_count:
start[len(start)-1-i] += 1
return(start, i, max_count)
else:
for j in range (len(start)):
if start[len(start)-1-i-j] == max_count:
start[len(start)-1-i-j] = 1
else:
start[len(start)-1-i-j] += 1
return(start, i, max_count)
def all_values(fresh_start, i, max_count):
number_list.append(fresh_start)
new_values = counter(fresh_start,i,max_count)
if new_values != None:
all_values(*new_values)
When I run all_values([1,1,1],0,3) and print number_list, though, I get:
[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1],
[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1],
[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1],
[1, 1, 1], [1, 1, 1], [1, 1, 1]]
Which is unfortunate. Doubly so knowing that if I replace the first line of all_values with
print(fresh_start)
I get exactly what I'm after:
[1, 1, 1]
[1, 1, 2]
[1, 1, 3]
[1, 2, 1]
[1, 2, 2]
[1, 2, 3]
[1, 3, 1]
[1, 3, 2]
[1, 3, 3]
[2, 1, 1]
[2, 1, 2]
[2, 1, 3]
[2, 2, 1]
[2, 2, 2]
[2, 2, 3]
[2, 3, 1]
[2, 3, 2]
[2, 3, 3]
[3, 1, 1]
[3, 1, 2]
[3, 1, 3]
[3, 2, 1]
[3, 2, 2]
[3, 2, 3]
[3, 3, 1]
[3, 3, 2]
[3, 3, 3]
I have already tried making a copy of fresh_start (by way of temp = fresh_start) and appending that instead, but with no change in the output.
Can anyone offer any insight as to what I might do to fix my code? Feedback on how the problem could be simplified would be welcome as well.
Thanks a lot!
temp = fresh_start
does not make a copy. Appending doesn't make copies, assignment doesn't make copies, and pretty much anything that doesn't say it makes a copy doesn't make a copy. If you want a copy, slice it:
fresh_start[:]
is a copy.
Try the following in the Python interpreter:
>>> a = [1,1,1]
>>> b = []
>>> b.append(a)
>>> b.append(a)
>>> b.append(a)
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> b[2][2] = 2
>>> b
[[1, 1, 2], [1, 1, 2], [1, 1, 2]]
This is a simplified version of what's happening in your code. But why is it happening?
b.append(a) isn't actually making a copy of a and stuffing it into the array at b. It's making a reference to a. It's like a bookmark in a web browser: when you open a webpage using a bookmark, you expect to see the webpage as it is now, not as it was when you bookmarked it. But that also means that if you have multiple bookmarks to the same page, and that page changes, you'll see the changed version no matter which bookmark you follow.
It's the same story with temp = a, and for that matter, a = [1,1,1]. temp and a are "bookmarks" to a particular array which happens to contain three ones. And b in the example above, is a bookmark to an array... which contains three bookmarks to that same array that contains three ones.
So what you do is create a new array and copy in the elements of the old array. The quickest way to do that is to take an array slice containing the whole array, as user2357112 demonstrated:
>>> a = [1,1,1]
>>> b = []
>>> b.append(a[:])
>>> b.append(a[:])
>>> b.append(a[:])
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> b[2][2] = 2
>>> b
[[1, 1, 1], [1, 1, 1], [1, 1, 2]]
Much better.
When I look at the desired output I can't help but think about using one of the numpy grid data production functions.
import numpy
first_column, second_column, third_column = numpy.mgrid[1:4,1:4,1:4]
numpy.dstack((first_column.flatten(),second_column.flatten(),third_column.flatten()))
Out[23]:
array([[[1, 1, 1],
[1, 1, 2],
[1, 1, 3],
[1, 2, 1],
[1, 2, 2],
[1, 2, 3],
[1, 3, 1],
[1, 3, 2],
[1, 3, 3],
[2, 1, 1],
[2, 1, 2],
[2, 1, 3],
[2, 2, 1],
[2, 2, 2],
[2, 2, 3],
[2, 3, 1],
[2, 3, 2],
[2, 3, 3],
[3, 1, 1],
[3, 1, 2],
[3, 1, 3],
[3, 2, 1],
[3, 2, 2],
[3, 2, 3],
[3, 3, 1],
[3, 3, 2],
[3, 3, 3]]])
Of course, the utility of this particular approach might depend on the variety of input you need to deal with, but I suspect this could be an interesting way to build the data and numpy is pretty fast for this kind of thing. Presumably if your input list has more elements you could have more min:max arguments fed into mgrid[] and then unpack / stack in a similar fashion.
Here is a simplified version of your program, which works. Comments will follow.
number_list = []
def _adjust_counter_value(counter, n, max_count):
"""
We want the counter to go from 1 to max_count, then start over at 1.
This function adds n to the counter and then returns a tuple:
(new_counter_value, carry_to_next_counter)
"""
assert max_count >= 1
assert 1 <= counter <= max_count
# Counter is in closed range: [1, max_count]
# Subtract 1 so expected value is in closed range [0, max_count - 1]
x = counter - 1 + n
carry, x = divmod(x, max_count)
# Add 1 so expected value is in closed range [1, max_count]
counter = x + 1
return (counter, carry)
def increment_counter(start, i, max_count):
last = len(start) - 1 - i
copy = start[:] # make a copy of the start
add = 1 # start by adding 1 to index
for i_cur in range(last, -1, -1):
copy[i_cur], add = _adjust_counter_value(copy[i_cur], add, max_count)
if 0 == add:
return (copy, i, max_count)
else:
# if we have a carry out of the 0th position, we are done with the sequence
return None
def all_values(fresh_start, i, max_count):
number_list.append(fresh_start)
new_values = increment_counter(fresh_start,i,max_count)
if new_values != None:
all_values(*new_values)
all_values([1,1,1],0,3)
import itertools as it
correct = [list(tup) for tup in it.product(range(1,4), range(1,4), range(1,4))]
assert number_list == correct
Since you want the counters to go from 1 through max_count inclusive, it's a little bit tricky to update each counter. Your original solution was to use several if statements, but here I have made a helper function that uses divmod() to compute each new digit. This lets us add any increment to any digit and will find the correct carry out of the digit.
Your original program never changed the value of i so my revised one doesn't either. You could simplify the program further by getting rid of i and just having increment_counter() always go to the last position.
If you run a for loop to the end without calling break or return, the else: case will then run if there is one present. Here I added an else: case to handle a carry out of the 0th place in the list. If there is a carry out of the 0th place, that means we have reached the end of the counter sequence. In this case we return None.
Your original program is kind of tricky. It has two explicit return statements in counter() and an implicit return at the end of the sequence. It does return None to signal that the recursion can stop, but the way it does it is too tricky for my taste. I recommend using an explicit return None as I showed.
Note that Python has a module itertools that includes a way to generate a counter series like this. I used it to check that the result is correct.
I'm sure you are writing this to learn about recursion, but be advised that Python isn't the best language for recursive solutions like this one. Python has a relatively shallow recursion stack, and does not automatically turn tail recursion into an iterative loop, so this could cause a stack overflow inside Python if your recursive calls nest enough times. The best solution in Python would be to use itertools.product() as I did to just directly generate the desired counter sequence.
Since your generated sequence is a list of lists, and itertools.product() produces tuples, I used a list comprehension to convert each tuple into a list, so the end result is a list of lists, and we can simply use the Python == operator to compare them.

Categories