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.
Related
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.
I am trying to create a python program to shuffle an array so that the horizontal and vertical rows never have a repeat number.
Input: [1,2,3,4]
Output:
1 2 3 4
2 3 4 1
3 4 1 2
4 1 2 3
My program calculates the shifting of each element correctly, but when it appends the list to the output list, the output list only has repeat copies of the last item in the list.
def numbers(list_of_numbers):
finalValues = [list_of_numbers]
#print(list_of_numbers)
for i in range(1,len(list_of_numbers)):
print("Last array of final: ", finalValues[-1])
tempArray = finalValues[-1]
print("Temp Array: ",tempArray)
temp = tempArray[0]
for j in range(0,len(list_of_numbers)-1):
tempArray[j] = tempArray[j+1]
tempArray[-1] = temp
finalValues.append(tempArray)
print("Final Values: ",finalValues)
return finalValues
numbers([1,2,3,4])
Program Output
[[4, 1, 2, 3], [4, 1, 2, 3], [4, 1, 2, 3], [4, 1, 2, 3]]
Correct Output
[[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]]
The problem comes from the line:
tempArray = finalValues[-1]
You don't create a copy of the previous list, but only a new name to refer to it. After that, all changes you make to tempArray are actually changes to this list, and when you finally do:
finalValues.append(tempArray)
you just add another reference to this same list in finalValues.
In the end, finalValues contains 4 references to the same list, which you can access with finalValues[0], finalValues[1]...
What you need is to create a new list by copying the previous one. One way to do it is to use a slice:
tempArray = finalValues[-1][:]
You can find other ways to close or copy a list in this question
And so, the complete code gives the expected output:
Last array of final: [1, 2, 3, 4]
Temp Array: [1, 2, 3, 4]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1]]
Last array of final: [2, 3, 4, 1]
Temp Array: [2, 3, 4, 1]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2]]
Last array of final: [3, 4, 1, 2]
Temp Array: [3, 4, 1, 2]
Final Values: [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
Thierry has provided a very comprehensive explanation of why your code doesn't work as you expect. As such it is the best answer to your question.I have added my answer just as an example of you you can code this in a less complex way .
create the 2d list with the first index as list of numbers. for each iteration take the last index of temp and slice from index 1 to the end then add on index 0.
then return the list
def numbers(list_of_numbers):
temp = [list_of_numbers]
for _ in range(1, len(list_of_numbers)):
temp.append(temp[-1][1:] + temp[-1][0:1])
return temp
print(numbers([1,2,3,4]))
OUTPUT
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]
The problems is in shallow assignment of arrays. You should make deep copy, to really clone arrays, to make them independent.
I did it in your own code. There are a few changes of your code:
import copy that it have been added to first row.
Three usages of copy.deepcopy function instead of =(simple assignment).
import copy
def numbers(list_of_numbers):
finalValues = copy.deepcopy([list_of_numbers])
#print(list_of_numbers)
for i in range(1,len(list_of_numbers)):
print("Last array of final: ", finalValues[-1])
tempArray = copy.deepcopy(finalValues[-1])
print("Temp Array: ",tempArray)
temp = tempArray[0]
for j in range(0,len(list_of_numbers)-1):
tempArray[j] = tempArray[j+1]
tempArray[-1] = temp
finalValues.append(copy.deepcopy(tempArray))
print("Final Values: ",finalValues)
return finalValues
numbers([1,2,3,4])
Program Output
[[4, 1, 2, 3], [4, 1, 2, 3], [4, 1, 2, 3], [4, 1, 2, 3]]
Program Output
[[1,2,3,4], [2,3,4,1], [3,4,1,2], [4,1,2,3]]
Warning: this question is not what you think!
Suppose I have a string like this (Python):
'[[1, 2], [2, 3], [0, 3]]'
Now suppose further that I have the permutation of the characters 0, 1, 2, 3 which swaps 0 and 1, as well as (separately) 2 and 3. Then I would wish to obtain
'[[0, 3], [3, 2], [1, 2]]'
from this. As another example, suppose I want to use the more complicated permutation where 1 goes to 2, 2 goes to 3, and 3 goes to 1? Then I would desire the output
'[[2, 3], [3, 1], [0, 1]]'
Question: Given a permutation (encoded however one likes) of characters/integers 0 to n-1 and a string containing (some of) them, I would like a function which takes such a string and gives the appropriate resulting string where these characters/integers have been permuted - and nothing else.
I have been having a lot of difficult seeing whether there is some obvious use of re or even just indexing that will help me, because usually these replacements are sequential, which would obviously be bad in this case. Any help will be much appreciated, even if it makes me look dumb.
(If someone has an idea for the original list [[1, 2], [2, 3], [0, 3]], that is fine too, but that is a list of lists and presumably more annoying than a string, and the string would suffice for my purposes.)
Here's a simple solution using a regular expression with callback:
import re
s = '[[1, 2], [2, 3], [0, 3]]'
map = [3, 2, 1, 0]
print(re.sub('\d+', # substitute all numbers
lambda m : str(map[int(m.group(0))]), # ... with the mapping
s # ... for string s
)
)
# output: [[2, 1], [1, 0], [3, 0]]
Well I think in general you'll need to use a working memory copy of the resultant to avoid the sequential issue you mention. Also converting to some structured data format like an array to work in makes things much easier (you don't say so but your target string is clearly a stringified array so I'm taking that for granted). Here is one idea using eval and numpy:
import numpy as np
s = '[[2, 3], [3, 1], [0, 1]]'
a = np.array(eval(s))
print('before\n', a)
mymap = [1,2,3,0]
a = np.array([mymap[i] for i in a.flatten()]).reshape(a.shape)
print('after\n', a)
Gives:
before
[[2 3]
[3 1]
[0 1]]
after
[[3 0]
[0 2]
[1 2]]
permutation = {'0':'1', '1':'0', '2':'3', '3':'2'}
s = '[[1, 2], [2, 3], [0, 3]]'
rv = ''
for c in s:
rv += permutation.get(c, c)
print(rv)
?
You can build a mapping of your desired transformations:
import ast
d = ast.literal_eval('[[1, 2], [2, 3], [0, 3]]')
m = {1: 2, 2: 3, 3: 1}
new_d = [[m.get(i) if i in m else
(lambda x:i if not x else x[0])([a for a, b in m.items() if b == i]) for i in b] for b in d]
Output:
[[2, 3], [3, 1], [0, 1]]
For the first desired swap:
m = {0:1, 2:3}
d = ast.literal_eval('[[1, 2], [2, 3], [0, 3]]')
new_d = [[m.get(i) if i in m else
(lambda x:i if not x else x[0])([a for a, b in m.items() if b == i]) for i in b] for b in d]
Output:
[[0, 3], [3, 2], [1, 2]]
This is absolutely inelegant regarding the quality of this forum I confess but here is my suggestion just to help:
string = '[[1, 2], [2, 3], [0, 3]]'
numbers = dict(zero = 0, one = 1, two = 2, three=3, four = 4, five = 5, six=6, seven=7, height=8, nine = 9)
string = string.replace('0', 'one').replace('1', 'zero').replace('2','three').replace('3', 'two')
for x in numbers.keys():
string = string.replace(x, str(numbers[x]))
[[0, 3], [3, 2], [1, 2]]
Is there a method similar to str.replace which can do the following:
>> replace(sequence=[0,1,3], old=[0,1], new=[1,2])
[1,2,3]
It should really act like str.replace : replacing a "piece" of a sequence by another sequence, not map elements of "old" with "new" 's ones.
Thanks :)
No, I'm afraid there is no built-in function that does this, however you can create your own!
The steps are really easy, we just need to slide a window over the list where the width of the window is the len(old). At each position, we check if the window == to old and if it is, we slice before the window, insert new and concatenate the rest of the list on after - this can be done simply be assigning directly to the old slice as pointed out by #OmarEinea.
def replace(seq, old, new):
seq = seq[:]
w = len(old)
i = 0
while i < len(seq) - w + 1:
if seq[i:i+w] == old:
seq[i:i+w] = new
i += len(new)
else:
i += 1
return seq
and some tests show it works:
>>> replace([0, 1, 3], [0, 1], [1, 2])
[1, 2, 3]
>>> replace([0, 1, 3, 0], [0, 1], [1, 2])
[1, 2, 3, 0]
>>> replace([0, 1, 3, 0, 1], [0, 1], [7, 8])
[7, 8, 3, 7, 8]
>>> replace([1, 2, 3, 4, 5], [1, 2, 3], [1, 1, 2, 3])
[1, 1, 2, 3, 4, 5]
>>> replace([1, 2, 1, 2], [1, 2], [3])
[3, 3]
As pointed out by #user2357112, using a for-loop leads to re-evaluating replaced sections of the list, so I updated the answer to use a while instead.
I tried this but before using this method read this about eval() by Ned :
import re
import ast
def replace(sequence, old, new):
sequence = str(sequence)
replace_s=str(str(old).replace('[', '').replace(']', ''))
if '.' in replace_s:
replace_ss=list(replace_s)
for j,i in enumerate(replace_ss):
if i=='.':
try:
replace_ss[0]=r"\b"+ replace_ss[0]
replace_ss[j]=r".\b"
except IndexError:
pass
replace_s="".join(replace_ss)
else:
replace_s = r"\b" + replace_s + r"\b"
final_ = str(new).replace('[', '').replace(']', '')
return ast.literal_eval(re.sub(replace_s, final_, sequence))
print(replace([0, 1, 3], [0, 1], [1, 2]))
output:
[1, 2, 3]
I'm trying to write a recursive function in Python that returns branches of a tree as lists, given depth or max_sum of a branch. I'm really frustrated by this. Maybe there are easier implementations with classes or generators? Below is the detailed description of the function behaviour i want to achieve.
func(data, depth)
'''Accepts a list with numbers > 0 and depth, i.e. max elements per list;
returns each branch of a tree'''
----------Examples--------------
Input: func([2, 1], depth=2)
Output: [[2, 2], [2, 1], [1, 2], [1, 1]]
Input: func([3, 2, 1], depth=2)
Output: [[3, 3], [3, 2], [3, 1]
[2, 3], [2, 2], [2, 1]
[1, 3], [1, 2], [1, 1]]
Input: func([2, 1], depth=3)
Output: [[2, 2, 2], [2, 2, 1], [2, 1, 2], [2, 1, 1],
[1, 2, 2], [1, 2, 1], [1, 1, 2], [1, 1, 1]]
Picture for the second example
Picture for the third example
Here's the code i wrote, which works only for the first example, it's horrible and i really feel ashamed of it :/ I tried dozens of approaches using classes and generators, but i'm not very familiar with those and the code only returned half of the options even for the first example.
tree = []
node_list = [2, 1]
def make_branch(depth=2, branch=None, d={0:2, 1:1}, switch=False, count=0):
#print(count)
if branch is None:
branch = []
for i in range(2):
#print(i)
if switch:
branch.append(d[i+1])
switch=False
else:
branch.append(d[i])
if len(branch) >= depth:
tree.append(branch)
print(branch)
return
make_branch(count= count + 1, branch=branch)
#print(-count)
branch = branch[:-1]
for i in range(len(node_list)):
if i % 2 == 0:
make_branch()
else:
make_branch(switch=True)
print(tree)
I don't see why you want to relate this to traversing a tree. Your task basically is just generating all permutations (with replacement) - which is identical to the Cartesian product with a fixed set - of a given length over a set of numbers.
In Python you can do it as follows:
import itertools
for i in itertools.product([1,2], repeat=3):
print i
This would e.g. output your third example. Just note that each output is a tuple and not a list - so you may want to convert them.
The simplest implementation would probably work like this:
def prod(lst, depth, buf=''):
if depth == 0:
print buf
return
for elem in lst:
prod(lst, depth - 1, buf + str(elem))
prod([1,2], 3)
print
prod([1,2,3], 2)
Output:
111
112
121
122
211
212
221
222
11
12
13
21
22
23
31
32
33