Related
I think I am trying to do something quite fundamental and quite simple. For this reason, I was sure Stack Overflow would already have a post regarding this task but I guess not? Maybe it is an inconsequential concept? Apologies if a post for this already exists. I couldn't find it.
Here is what I want to accomplish: Given a list length n and a maximum element value m, generate all of the permutations of the list with each element varying between 0 and m.
QUESTIONS: 1. Is there a way to do this recursively? 2. Is recursion optimal (in terms of computational resources, O time, etc.) for this concept or is iteration better? 3. Is there a better way (less complicated) to achieve this using iteration (see my code below)?
more information found below
I have edited my code and the two examples to produce and exhibit complete solutions
Here are two examples:
Example 1: n = 3, m = 2
Output:
[0, 0, 0]
[0, 0, 1]
[0, 0, 2]
[0, 1, 0]
[0, 1, 1]
[0, 1, 2]
[0, 2, 0]
[0, 2, 1]
[0, 2, 2]
[1, 0, 0]
[1, 0, 1]
[1, 0, 2]
[1, 1, 0]
[1, 1, 1]
[1, 1, 2]
[1, 2, 0]
[1, 2, 1]
[1, 2, 2]
[2, 0, 0]
[2, 0, 1]
[2, 0, 2]
[2, 1, 0]
[2, 1, 1]
[2, 1, 2]
[2, 2, 0]
[2, 2, 1]
[2, 2, 2]
Example 1: n = 2, m = 4
Output:
[0, 0]
[0, 1]
[0, 2]
[0, 3]
[0, 4]
[1, 0]
[1, 1]
[1, 2]
[1, 3]
[1, 4]
[2, 0]
[2, 1]
[2, 2]
[2, 3]
[2, 4]
[3, 0]
[3, 1]
[3, 2]
[3, 3]
[3, 4]
[4, 0]
[4, 1]
[4, 2]
[4, 3]
[4, 4]
My intuition tells me this can be done recursively but I can't think of how to do it (I'm a beginner programmer). Currently, I have a solution to achieve this iteratively:
def permute(curr_permute,max_num,reset_flgs,reset_ind):
'''
Increment Logic Structure
'''
perm_ind = 0
max_val_flgs = [0]*len(curr_permute)
for c_i in range(len(curr_permute)):
if ((curr_permute[c_i] == max_num) and (c_i < (len(curr_permute)-1))):
if ((reset_ind == c_i) and (reset_flgs[c_i] == 1)):
reset_ind += 1
reset_flgs[c_i] = 0
max_val_flgs[c_i] = 1
continue
else:
perm_ind += 1
max_val_flgs[c_i] = 1
elif (c_i == (len(curr_permute)-1)):
if (curr_permute[c_i] == max_num):
perm_ind = c_i
max_val_flgs[c_i] = 1
else:
perm_ind = c_i
elif (curr_permute[c_i] < max_num):
perm_ind += 1
'''
Reverse the lists
'''
max_val_flgs.reverse()
curr_permute.reverse()
reset_flgs.reverse()
'''
Reset Logic Structure
'''
for n_i in range(len(curr_permute)):
if (max_val_flgs[n_i] == 0):
break
elif ((max_val_flgs[n_i] == 1) and (reset_flgs[n_i] == 1)):
curr_permute[n_i] = 0
perm_ind += -1
'''
Reverse the lists
'''
curr_permute.reverse()
reset_flgs.reverse()
'''
Apply the permutation increment
'''
curr_permute[perm_ind] += 1
return(curr_permute,reset_flgs,reset_ind)
def Permutation_Generation():
n = 2
m = 4
curr_permute = [0]*n
reset_flgs = [1]*n
reset_ind = 0
All_Permutations = [list(curr_permute)]
while (sum(curr_permute) < (n*m)):
print(curr_permute)
[curr_permute,reset_flgs,reset_ind] = permute(curr_permute,m,reset_flgs,reset_ind)
All_Permutations.append(list(curr_permute))
print(curr_permute)
return(All_Permutations)
Apologies for the garbage code. Once I came up with a way to do it successfully, I didn't make much effort to clean it up or make it more efficient. My guess is this code is too complicated for the concept I am attempting to address.
I don't think your output with n and m are 3, 2, respectively, really make sense. After 6th row [0, 2, 0], shouldn't it be followed by [0, 2, 1] instead of [1, 0, 0]? Same also happened after 13th row.
Anyway here is an recursive alterantive:
n = 3
m = 2
def permutation(n, m):
if n <= 0:
yield []
else:
for i in range(m+1):
for j in permutation(n-1, m):
yield [i] + j
# or even shorter
def permutation(n, m):
return [[i] + j for i in range(m + 1) for j in permutation(n - 1, m)] if n > 0 else []
for i in permutation(n, m):
print(i)
Output:
[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 0], [0, 1, 1], ..., [2, 1, 0], [2, 1, 1], [2, 1, 2], [2, 2, 0], [2, 2, 1], [2, 2, 2]]
3. You want to get all permutations. The number of permutations given n and m is (m+1)^n.
Because you actually want to print all of them the time complexity is also O((m+1)^n), which is what you get when doing it iteratively.
1+2. I don't think you should use recursion to do it, O((m+1)^n) is the optimal time you can do it and it's what you get when using iterations. recursion will also take more memory for it's memory stack.
The question seems to be answered, but I want to contribute an alternative for the permutation by iteration that is a bit simpler. Here I use list comprehensions, formatted string literals (the f'string' strings) and the eval built-in method. Hope this perspective is helpful to you (somehow?).
def get_permutations_list(array_size, max_value):
'''Returns a list of arrays that represent all permutations between zero
and max_value'''
array_member =''
for_loops=''
#Does a loop iteration for each member of the permutation list template
for i in range(array_size):
#adds a new member in each permutation
array_member += f'x{i}, '
#adds a new corresponding for loop
for_loops+=" for x{i} in range({value})".format(i=i,
value=max_value)
output = f'[[{array_member}] {for_loops}]' #combines it all together
return eval(output)
a = get_permutations_list(array_size=2, max_value=2)
print(a)
#Result: [[0,0],[0,1],[1,0],[1,1]]
I created a function f which uses a 2-dimension list as parameter, but after this function the list does not change at all.
As the code below:
def f(t: [[int]]):
for eachrow in t:
eachrow = eachrow[1:]
eachrow.append(0)
A = [[2, 10, 0], [3, 1, 2], [3, 2, 1]]
f(A)
print(A) # -> [[2, 10, 0], [3, 1, 2], [3, 2, 1]]
Assigning to eachrow in eachrow = eachrow[1:] overwrites it. So to remove the first element, you could use del instead, or row.pop or slice assignment.
def f(t):
for row in t:
del row[0] # OR row.pop(0) OR row[:] = row[1:]
row.append(0)
A = [[2, 10, 0], [3, 1, 2], [3, 2, 1]]
f(A)
print(A) # -> [[10, 0, 0], [1, 2, 0], [2, 1, 0]]
If you print out the results of your changes to eachrow, you'll see that you ARE updating the eachrow variable, but that doesn't affet the original t variable.
def f(t):
for eachrow in t:
eachrow = eachrow[1:]
eachrow.append(0)
print(eachrow)
>>> f(A)
[10, 0, 0]
[1, 2, 0]
[2, 1, 0]
If you want to affect the array itself, you should modify the array like so:
def f(t):
for row_number in range(len(t)):
t[row_number] = t[row_number][1:]
t[row_number].append(0)
x = [2, 1, 2, 0, 1, 2, 2]
I want to splice the above list into sublists of length = [1, 2, 3, 1]. In other words, I want my output to look something like this:
[[2], [1, 2], [0, 1, 2], [2]]
where my first sublist is of length 1, the second sublist is of length 2, and so forth.
You can use itertools.islice here to consume N many elements of the source list each iteration, eg:
from itertools import islice
x = [2, 1, 2, 0, 1, 2, 2]
length = [1, 2, 3, 1]
# get an iterable to consume x
it = iter(x)
new_list = [list(islice(it, n)) for n in length]
Gives you:
[[2], [1, 2], [0, 1, 2], [2]]
Basically we want to extract certain lengths of substrings.
For that we need a start_index and an end_index. The end_index is your start_index + the current length which we want to extract:
x = [2, 1, 2, 0, 1, 2, 2]
lengths = [1,2,3,1]
res = []
start_index = 0
for length in lengths:
res.append(x[start_index:start_index+length])
start_index += length
print(res) # [[2], [1, 2], [0, 1, 2], [2]]
Added this solution to the other answer as it does not need any imported modules.
You can use the following listcomp:
from itertools import accumulate
x = [2, 1, 2, 0, 1, 2, 2]
length = [1, 2, 3, 1]
[x[i - j: i] for i, j in zip(accumulate(length), length)]
# [[2], [1, 2], [0, 1, 2], [2]]
def fn():
theList = []
for rev in range(5, 0, -1):
theList.append(rev)
print(theList)
fn()
I don't understand, why this won't execute?
My goal is to print something like this
[5,4,3,2,1,0]
[4,3,2,1,0]
[3,2,1,0]
[2,1,0]
[1,0]
[0]
Edit 1. Okey i added the comma(,) but the result is this
[5]
[5, 4]
[5, 4, 3]
[5, 4, 3, 2]
[5, 4, 3, 2, 1]
Which is not what i am looking for
You can get your output like this:
def fn():
theList = list(range(5, -1, -1))
for idx in range(len(theList)):
print(theList[idx:])
fn()
Output:
[5, 4, 3, 2, 1, 0]
[4, 3, 2, 1, 0]
[3, 2, 1, 0]
[2, 1, 0]
[1, 0]
[0]
Your code is using the wrong approach. Basically, your output shows that the list is full initially and goes on popping one element from the left on each iteration. Your approach starts with an empty list and adds an element on each iteration.
Also, range(5, 0, -1) is not the list you think it is. This is because the range function ignores the end value which is 0 here.
If you did this list(range(5, 0, -1)), you'd get [5, 4, 3, 2, 1] which obviously doesn't contain 0. So, to get the list you want, you'd have to do list(range(5, -1, -1)) like in the code above.
1)There is a typo in your function:
for rev in range(5, 0, -1):
2) you need to use your rev:
for rev in range(5, -1, -1):
print(range(rev,-1,-1))
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.