Preserving the current value of a local variable when there is recursion - python

I'm writing a function to flatten a nested array (Python list). e.g turn [1,2,[3]] into [1,2,3], [[1,2,[3]],4] into [1,2,3,4] etc.
I have the following:
def flatten_array(array):
flattened_array = []
for item in array:
if not isinstance(item, list):
flattened_array.append(item)
else:
flatten_array(item)
return flattened_array
So the idea is to have the function be recursive, to handle situations where there is nesting to an unknown depth. My problem is that flattened_array is getting re-initialized each time a nested list is encountered (when flatten_array is called recursively).
print flatten_array([1,2,[3]])
[1,2]
How can I maintain the state of flattened_array when recursive calls are made?

Change the lines
else:
flatten_array(item)
to
else:
flattened_array+=flatten_array(item)
So the full function reads like
def flatten_array(array):
flattened_array = []
for item in array:
if not isinstance(item, list):
flattened_array.append(item)
else:
flattened_array+=flatten_array(item)
return flattened_array
this gives
flatten_array([1,2,[3]]) # [1,2,3]
flatten_array([1,2,[3,[4,5]]]) # [1,2,3,4,5]
flatten_array([1,2,[3,[4,5]],6,7,[8]]) # [1,2,3,4,5,6,7,8]
Your original code is not doing anything with the recursive call. You get back the result on the list, but just discard it. What we want to do is attach it to the end of the existing list.
Additionally, if you don't want to keep creating temporary arrays, we can create one array with the first call to the function and just append to it.†
def flatten_array(array,flattened_array=None):
if flattened_array is None:
flattened_array = []
for item in array:
if not isinstance(item,list):
flattened_array.append(item)
else:
flatten_array(item,flattened_array)
return flattened_array
The results of this version are the same, and it can be used the same way, but in the original, each call to the function creates a new empty array to work with. Normally this isn't a problem, but depending on the depth or how large the sub-arrays are this can build up in memory.
This version flattens the array into a given array. When called with just the input (like flatten_array([1,2,[3]])), it creates an empty array to work with, otherwise it just adds to the given array (thus the recursive call just needs to give the array to add to), modifying it in place.
This has the advantage of allowing you to add to an existing array if we want:
a = [1,2,3]
b = [2,3,[4]] # we want to add flatten this to the end of a
flatten_array(b,a) # we don't bother catching the return result here
print(a) # [1,2,3,2,3,4]
† There is a subtle point here. You may ask why we didn't define the function as def flatten_array(array,flattened_array=[]) and get arid of the test inside the function. Try that and call the function a few times. What happens is that the default value is created once at function definition and not each time the function is called. This means that the default array which is modified in place is shared by each function call, resulting in it accumulating the results.
This is likely not what we want. By setting the default value to None and creating a new empty array inside the function each time, we ensure that each call to the function has a unique empty array to work with.

Related

Python List Append not working on sliced lists

I have a list a = [1,2,3,4,5]
I don't understand why the following code doesn't produce [2,3,4,5,1]
a[1:].append(a[0])
I tried reading up on the append() method as well as list slicing in Python, but found no satisfactory response.
a[1:] gives you a whole new list, but you're not assigning it to any variable so it it just thrown away after that line. You should assign it to something (say, b) and then append to it (otherwise append would change the list but return nothing):
a = [1,2,3,4,5]
b = a[1:]
b.append(a[0])
And now b is your desired output [2,3,4,5,1]
I think here you just not really understand append function. Just like my answer in Passing a variable from one file into another as a class variable after inline modification, append is an in-place operation which just update the original list and return None.
Your chain call a[1:].append(a[0]) will return the last call return value in the chain, so return append function value, which is None.
Just like #flakes comment in another answer, a[1:] + a[:1] will return your target value. Also you can try a[1:][1:][1:] which will return some
result. So the key point is the append function is in-place function.
See python more on list
You might have noticed that methods like insert, remove or sort that only modify the list have no return value printed – they return the default None. 1 This is a design principle for all mutable data structures in Python.
First, append the first element a[0] to the list
a.append(a[0])
and then exclude the first element
a[1:]

Python: Editing specific list indices from a for loop

I'm trying to loop through a list in Python, make some changes to it, and then output a result. Here's the function:
def scramble_bytes(self, ref_key):
"""
Uses ref_key as a reference to scramble self.
They must be equal-length lists of bytes
"""
if len(self) != len(ref_key):
return "Inputs to scramble_bytes must be equal length!"
scrambles_needed = range(len(self))
scramble_length = len(self)
output = self
for i in scrambles_needed:
scramble_selector = int.from_bytes(ref_key[i], byteorder='big')
scrambler_byte = int.from_bytes(output[(scramble_selector + i) % scramble_length], byteorder='big')
scrambled_byte = int.from_bytes(output[i], byteorder='big')
result_scramble = scrambler_byte ^ scrambled_byte
output[i] = result_scramble.to_bytes(1, byteorder="big")
return output
To clarify, self and ref_key are both lists of bytes- such as [b'a', b'c', b'xb0']
I know that it's not common practice to edit a list that's being looped through in Python, but in this case I need to do it, because the entire process needs to be reversible through another function that only gets output and ref_key as its inputs. If I append to a new list, the function will not be reversible.
I suspect that the problem has something to do with python namespaces- output[i] would create a new local variable in the for loop. If this is indeed the issue, how do I solve it?
TLDR: make a copy of the list to be mutated in a loop.
Making a copy of the list to be processed will not make the function ireverisble. A mutated list is inherently a ``new list" (in the math sense).
Reversing the process should still be possible in a different function.
It is best for you to make a copy of the list being processed.
This code seem to be a method trying to modify its object directly. Is this what you really want? If that is the case look in to metaclasses.
Assuming you want to modify an attribute of an object, you can make an attribute self.original_ which represents the list to be scrambled, and just the scramble function. This way you do not need the unsrambe function as you can update the attributes as needed.

Python, change value of the arguments within the function

I'm trying to change the value of the list that i put as argument in the function.
this is the code:
def shuffle(xs,n=1):
if xs: #if list isn't empty
if n>0:
#gets the index of the middle of the list
sizel=len(xs)
midindex=int((sizel-1)/2)
for times in range(n):
xs=interleave(xs[0:midindex],xs[midindex:sizel])
return None
The interleave code returns a list with the values of both lists mixed up.
However when i run:
t=[1,2,3,4,5,6,7]
shuffle(t,n=2)
print t
The list t didn't changed it's order. The function needs to return None so i can jst use t=shuffle(t,n). There's anyway i can do this?
Your problem is right here:
xs=interleave(xs[0:midindex],xs[midindex:sizel])
You're making slices of the list to pass to your interleave() function. These are essentially copies of part of the list. There's no way that what comes back from the function can be anything than a different list from xs.
Fortunately, you can just reassign the new list you get back into the original list. That is, keep xs pointing to the same list, but replace all the items in it with what you get back from the interleave() function.
xs[:]=interleave(xs[0:midindex],xs[midindex:sizel])
This is called a slice assignment. Since xs remains the same list that was passed in, all references to the list outside the function will also see the changes.
xs is a reference local to the function, and is independant of t. When you reassign xs, t still points to the original list.
Since you must not return anything from the function, a workaround is to keep a reference to the original list and repopulate it using slice assignment:
orig_xs = xs
# do stuff here
orig_xs[:] = xs

Python Function Not Working

I am trying to create a function, new_function, that takes a number as an argument.
This function will manipulate values in a list based on what number I pass as an argument. Within this function, I will place another function, new_sum, that is responsible for manipulating values inside the list.
For example, if I pass 4 into new_function, I need new_function to run new_sum on each of the first four elements. The corresponding value will change, and I need to create four new lists.
example:
listone=[1,2,3,4,5]
def new_function(value):
for i in range(0,value):
new_list=listone[:]
variable=new_sum(i)
new_list[i]=variable
return new_list
# running new_function(4) should return four new lists
# [(new value for index zero, based on new_sum),2,3,4,5]
# [1,(new value for index one, based on new_sum),3,4,5]
# [1,2,(new value for index two, based on new_sum),4,5]
# [1,2,3,(new value for index three, based on new_sum),5]
My problem is that i keep on getting one giant list. What am I doing wrong?
Fix the indentation of return statement:
listone=[1,2,3,4,5]
def new_function(value):
for i in range(0,value):
new_list=listone[:]
variable=new_sum(i)
new_list[i]=variable
return new_list
The problem with return new_list is that once you return, the function is done.
You can make things more complicated by accumulating the results and returning them all at the end:
listone=[1,2,3,4,5]
def new_function(value):
new_lists = []
for i in range(0,value):
new_list=listone[:]
variable=new_sum(i)
new_list[i]=variable
new_lists.append(new_list)
return new_lists
However, this is exactly what generators are for: If you yield instead of return, that gives the caller one value, and then resumes when he asks for the next value. So:
listone=[1,2,3,4,5]
def new_function(value):
for i in range(0,value):
new_list=listone[:]
variable=new_sum(i)
new_list[i]=variable
yield new_list
The difference is that the first version gives the caller a list of four lists, while the second gives the caller an iterator of four lists. Often, you don't care about the difference—and, in fact, an iterator may be better for responsiveness, memory, or performance reasons.*
If you do care, it often makes more sense to just make a list out of the iterator at the point you need it. In other words, use the second version of the function, then just writes:
new_lists = list(new_function(4))
By the way, you can simplify this by not trying to mutate new_list in-place, and instead just change the values while copying. For example:
def new_function(value):
for i in range(value):
yield listone[:i] + [new_sum(i)] + listone[i+1:]
* Responsiveness is improved because you get the first result as soon as it's ready, instead of only after they're all ready. Memory use is improved because you don't need to keep all of the lists in memory at once, just one at a time. Performance may be improved because interleaving the work can result in better cache behavior and pipelining.

Recursion in Python 3.2

I am trying to wrap my head around recursion and have posted a working algorithm to produce all the subsets of a given list.
def genSubsets(L):
res = []
if len(L) == 0:
return [[]]
smaller = genSubsets(L[:-1])
extra = L[-1:]
new = []
for i in smaller:
new.append(i+extra)
return smaller + new
Let's say my list is L = [0,1], correct output is [[],[0],[1],[0,1]]
Using print statements I have narrowed down that genSubsets is called twice before I ever get to the for loop. That much I get.
But why does the first for loop initiate a value of L as just [0] and the second for loop use [0,1]? How exactly do the recursive calls work that incorporate the for loop?
I think this would actually be easier to visualize with a longer source list. If you use [0, 1, 2], you'll see that the recursive calls repeatedly cut off the last item from the list. That is, recusion builds up a stack of recursive calls like this:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0])
genSubsets([])
At this point it hits the "base case" of the recursive algorithm. For this function, the base case is when the list given as a parameter is empty. Hitting the base case means it returns an list containing an empty list [[]]. Here's how the stack looks when it returns:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0]) <- gets [[]] returned to it
So that return value gets back to the previous level, where it is saved in the smaller variable. The variable extra gets assigned to be a slice including only the last item of the list, which in this case is the whole contents, [0].
Now, the loop iterates over the values in smaller, and adds their concatenation with extra to new. Since there's just one value in smaller (the empty list), new ends up with just one value too, []+[0] which is [0]. I assume this is the value you're printing out at some point.
Then the last statement returns the concatenation of smaller and new, so the return value is [[],[0]]. Another view of the stack:
genSubsets([0,1,2])
genSubsets([0,1]) <- gets [[],[0]] returned to it
The return value gets assigned to smaller again, extra is [1], and the loop happens again. This time, new gets two values, [1] and [0,1]. They get concatenated onto the end of smaller again, and the return value is [[],[0],[1],[0,1]]. The last stack view:
genSubsets([0,1,2]) <- gets [[],[0],[1],[0,1]] returned to it
The same thing happens again, this time adding 2s onto the end of each of the items found so far. new ends up as [[2],[0,2],[1,2],[0,1,2]].
The final return value is [[],[0],[1],[0,1],[2],[0,2],[1,2],[0,1,2]]
I am no big fan of trying to visualize the entire call graph for recursive function to understand what they do.
I believe there is a much simpler way:
Enter fairy tale land where recursive functions do the right thing™.
Just assume that genSubsets(L) works:
# This computes the powerset of the list L minus the last element
smaller = genSubsets(L[:-1])
Because this magically worked, the only entries that are missing are those, that contain the last element.
This fragment constructs all those missing subsets:
new = []
for i in smaller:
new.append(i+extra)
Now we have those subsets containing the last element in new and we have those subsets not containing the last element in smaller.
It follows that we must now have all subsets, so we can return new + smaller.
The only thing left is the base case to make sure the recursion stops. Because the empty set (or list in this case) is an element of every power set, we can use that to stop the recursion: Requesting the powerset of an empty set is a set containing the empty set. So our base case is correct. Since every recursive step removes one element off the list, the base case must be encountered at some time.
Thus, the code really does produce the power set.
Note: The principle behind this is that of induction. If something works for some known n0, and we can prove that: The algorithm working for n implies it works for n+1, it must thus work for all n &geq; n0.

Categories

Resources