having trouble understanding this code - python

I just started learning recursion and I have an assignment to write a program that tells the nesting depth of a list. Well, I browsed around and found working code to do this, but I'm still having trouble understanding how it works. Here's the code:
def depth(L) :
nesting = []
for c in L:
if type(c) == type(nesting) :
nesting.append(depth(c))
if len(nesting) > 0:
return 1 + max(nesting)
return 1
So naturally, I start to get confused at the line with the append that calls recursion. Does anyone have a simple way of explaining what's going on here? I'm not sure what is actually being appended, and going through it with test cases in my head isn't helping. Thanks!
edit: sorry if the formatting is poor, I typed this from my phone

Let me show it to you the easy way, change the code like this:
(### are the new lines I added to your code so you can watch what is happening there)
def depth(L) :
nesting = []
for c in L:
if type(c) == type(nesting) :
print 'nesting before append', nesting ###
nesting.append(depth(c))
print 'nesting after append', nesting ###
if len(nesting) > 0:
return 1 + max(nesting)
return 1
Now lets make a list with the depth of three:
l=[[1,2,3],[1,2,[4]],'asdfg']
You can see our list has 3 element. one of them is a list, the other is a list which has another list in itself and the last one is a string. You can clearly see the depth of this list is 3 (i.e there are 2 lists nested together in the second element of the main list)
Lets run this code:
>>> depth(l)
nesting before append []
nesting after append [1]
nesting before append [1]
nesting before append []
nesting after append [1]
nesting after append [1, 2]
3
Piece of cake! this function appends 1 to the nesting. then if the element has also another list it appends 1 + maximum number in nesting which is the number of time function has been called itself. and if the element is a string, it skips it.
At the end, it returns the maximum number in the nesting which is the maximum number of times recursion happened, which is the number of time there is a list inside list in the main list, aka depth. In our case recursion happened twice for the second element + 1=3 as we expected.
If you still have problem getting it, try to add more print statements or other variables to the function and watch them carefully and eventually you'll get it.

So what this seems to be is a function that takes a list and calculates, as you put it, the nesting depth of it. nesting is a list, so what if type(c) == type(nesting) is saying is: if the item in list L is a list, run the function again and append it and when it runs the function again, it will do the same test until there are no more nested lists in list L and then return 1 + the max amount of nested lists because every list has a depth of 1.
Please tell me if any of this is unclear

Let's start with a couple of examples.
First, let's consider a list with only one level of depth. For Example, [1, 2, 3].
In the above list, the code starts with a call to depth() with L = [1, 2, 3]. It makes an empty list nesting. Iterates over all the elements of L i.e 1, 2, 3 and does not find a single element which passes the test type(c) == type(nesting). The check that len(nesting) > 0 fails and the code returns a 1, which is the depth of the list.
Next, let's take an example with a depth of 2, i.e [[1, 2], 3]. The function depth() is called with L = [[1, 2], 3] and an empty list nesting is created. The loop iterates over the 2 elements of L i.e [1, 2] , 3 and since type([1, 2]) == type(nesting), nesting.append(depth(c)) is called. Similar to the previous example, depth(c) i.e depth([1, 2]) returns a 1 and nesting now becomes [1]. After the execution of the loop, the code evaluates the test len(nesting) > 0 which results in True and 1 + max(nesting) which is 1 + 1 = 2 is returned.
Similarly, the code follows for the depth 3 and so on.
Hope this was helpful.

This algorithm visits the nested lists and adds one for each level of recursion. The call chain is like this:
depth([1, 2, [3, [4, 5], 6], 7]) =
1 + depth([3, [4, 5], 6]) = 3
1 + depth([4, 5]) = 2
1
Since depth([4,5]) never enters the if type(c) == type(nesting) condition because no element is a list, it returns 1 from the outer return, which is the base case.
In the case where, for a given depth, you have more than one nested list, e.g. [1, [2, 3], [4, [5, 6]], both the max depth of [2,3]and [4, [5, 6]] are appended on a depth call, of which the max is returned by the inside return.

Related

The second to the last line of code is confusing to me

I am quite new to python and programming in general so I am still trying to understand the details in practice. This is a problem I found online so I can practice nested loops more. If my question is missing anything or you do not understand my question, please let me know. I would like to get better at asking good questions as well.
list =[[1, 2], [3,4]]
m = 1
print(list)
for i in range(0, 2):
m *= 10
for j in range(0, 2):
list[i][j] *= m # This part right here.
print(list)
This is what prints on the terminal:
[[1, 2], [3, 4]]
[[10, 20], [300, 400]]
I was trying to go through this block of code step by step to make sure I understand it but this part is stumping me. I understand that the whole function of this nested for loop is to multiply the items in the 1st list with 10 and the 2nd list with 100. I also know what the *= m part is, the part that's confusing to me is the code right before that on the same line.
So far I tried to just copy this specific part in google and see if anything came up. I could not find anything that would make sense. I also tried to just run this whole line and see what printed (list[i][j] *= m)(I changed the variable to numbers obviously). That only came up with a type error... There are no type variables left in list[2]. I was trying to isolate it to see what just this part does but it apparently doesn't work like that. I guess i need to think outside the box a little more maybe.
If we take i to be 1 and j to be 1, list[i][j] would be 4. list[i] = [3,4] so what you're doing is finding index 1 of the list [3, 4]
list is a list containing nested lists.
list[0] is the list [1, 2]. list[1] is the list [3, 4].
When you use two indexes like list[i][j], it first gets the nested list list[i], then accesses the [j] element of that. So when i == 0 and j == 1, this accesses the list element containing 2.
*= m then multiplies that list element. So when i == 0 and m == 10, it multiplies the values in the first sublist by 10. Then when i == 1 and m == 100 it multiplies the values in the second sublist by 100.
list = [[1,2], [3,4]] is an array of arrays.
To get a single element, you have to subscript list twice, which is exactly what list[i][j] is.
list[0] returns [1,2], the 0-th element of the list, which is a sublist
list[0][0] returns 1, the 0-th element of the 0-th sublist
list[0][1] returns 2, the 1st element of the 0-th sublist
.
list[1] returns [3,4], the 1st element of the list, which is a sublist
list[1][0] returns 3, the 0-th element of the 1st sublist
list[1][1] returns 4, the 1st element of the 1st sublist
.
Also, the reason why list[2] doesn't return anything is because list only has two elements, which have the index 0 and 1, so trying to get the element at index 2 will not work

Unable to generate combination of numbers in python list using recursion

I am trying to generate a combination of numbers in python list using recursion
and my code is as follows
nums = [2,3,4,6]
def backtrck(temp, starting_index, nums):
if len(temp) == 2:
print(temp)
return
temp.append(nums[starting_index])
for i in range(starting_index + 1, len(nums)):
backtrck(temp, i, nums)
backtrck([], 0, nums)
for some reason, the above code is unable to generate the proper combinations.
Aim of the code: I want to generate all the combination of numbers starting with index 0 whose length should be equal to 2
expected output
[2, 3]
[2, 4]
[2, 6]
actual output
[2, 3]
[2, 3]
[2, 3]
[2, 3]
I don't understand what is going wrong with this recursion, I am hoping that someone could help me figure this out
Recursion is unnecessary when you can simply use for loop:
nums = [2,3,4,6]
def backtrck(starting_index, nums):
start = nums[starting_index]
for num in nums[starting_index + 1:]:
print([start, num])
backtrck(0, nums)
Output:
[2, 3]
[2, 4]
[2, 6]
where the slice nums[start_index + 1:] returns a list of all the elements of the nums list starting from one indice after the starting_index.
UPDATE
Since you've pointed out that the recursion was necessary in your code, simply replace the backtrck(temp, i, nums) recursive call with backtrck(i, nums, [temp[0], nums[i]]) to keep the starting index of the list:
nums = [2, 3, 4, 6]
def backtrck(starting_index, nums, temp=[]):
if len(temp) == 2:
print(temp)
return
temp.append(nums[starting_index])
for i in range(starting_index + 1, len(nums)):
backtrck(i, nums, [temp[0], nums[i]])
backtrck(0, nums)
Output:
[2, 3]
[2, 4]
[2, 6]
Note that I've changed the positional argument temp into a keyword argument. It will still work with temp as a positional argument, but it will be less practical if the temp list always starts out as empty.
What is wrong with your function:
After couple of recursive calls temp becomes [2,3] then on the next recursion your base case is met (len(temp) == 2:) and that instance of the function returns without adding anything. The next for loop iteration recurses and the same thing happens. Once temp is [2,3] it can never change.
How to fix it:
There are a number of problems with the structure of your function and it is not a simple one-line-fix. You need to figure out how to
when the base case is met
capture (or print) temp
return something meaningful to the previous function that it can use to continue making combinations
the function needs to act upon the return value from the recursive call
adding a for loop to a recursive procedure/process complicates things, maybe figure out how to do without it.
I would start over with the function. I don't know if you are asking someone to give you a completely new function so I'm going to search for questions regarding recursive solutions to find/generate list item combinations.
Here are some related SO Questions/Answers. If any of them solve your problem let us know so we can mark yours as a duplicate. Most don't have the taken two-at-a-time constraint but maybe you can adapt. there are many more.
Recursive function that returns combinations of size n chosen from list
Recursively find all combinations of list
python recursion list combinations without using generator
Using recursion to create a list combination
Loosely related:
Nth Combination
Finding all possible combinations of numbers to reach a given sum
Creating all possible k combinations of n items in C++ - not Python but algorithms might be useful.
When it comes to recursion, my advice is keep it simple and let the recursion do the work for you:
def backtrck(numbers):
if len(numbers) < 2:
return []
first, second, *rest = numbers
return [[first, second]] + backtrck([first] + rest)
nums = [2, 3, 4, 6]
print(*backtrck(nums), sep='\n')
OUTPUT
> python3 test.py
[2, 3]
[2, 4]
[2, 6]
>

python:two programs to delete val from nums

write a python program to delete val from array:
the first program is:
class Solution(object):
def removeElement(self,nums,val):
for x in nums:
if x == val:
nums.remove(val)
return len(nums)
when the nums is [3,3], the val is 3, the output is :1
the second program is :
class Solution(object):
def removeElement(self,nums,val):
while val in nums:
nums.remove(val)
return len(nums)
the nums is [3,3],the val is 3,the output is :0
could you please tell me the difference and reason
Python has a hard time iterating through something that is being changed during the iteration. It might fail or return unexpected results or never exit the iteration.
Because a container doesn't even keep track of iterators that are out
on it, much less hook even altering-method to loop over every such
iterator and somehow magically let each iterator know about the
alterations. It would be a lot subtle, complex code, and checks
slowing down very frequent operations.
nums=[3,4]
val=3
for x in nums:
print(x,nums)
if x > val:
nums.append(4)
The above code will not exit the iteration.So the correct way is to use a list comprehension to create a new list containing only the elements you don't want to remove:
print([i for i in nums if i!=val])
Or in-place alteration:
nums[:] = [i for i in nums if i!=val]
Hope this helps.
In the first example you are deleting an element inside the list while iterating over the elements of the list. That's a problem because you will run out of iterations after the first remove.
If the list is, for example, l = [1, 1, 2, 2] and you call removeElement(l, 2) after the first 2 is deleted, the length of l will be 3 and there won't be any iterations left (the list will be [1, 1, 2] and you will be at iteration number 3, that's why the loop stops and you get returned [1, 1, 2]).
In the second case, the while statement is: while there is still a 2 in the list, continue removing 2 from the list. This way, after the first iteration the l will look like [1, 1, 2] and there would still be a 2 inside it, so the loop continues and you get [1, 1].
You can apply this example on your case with l = [3, 3] and see if you understand it.
Hope that helped :)

Python: Change the parameter of the loop while the loop is running

I want to change a in the for-loop to [4,5,6].
This code just print: 1, 2, 3
a = [1,2,3]
for i in a:
global a
a = [4,5,6]
print i
I want the ouput 1, 4, 5, 6.
You'll need to clarify the question because there is no explanation of how you should derive the desired output 1, 4, 5, 6 when your input is [1, 2, 3]. The following produces the desired output, but it's completely ad-hoc and makes no sense:
i = 0
a = [1, 2, 3]
while i < len(a):
print(a[i])
if a[i] == 1:
a = [4, 5, 6]
i = 0 # edit - good catch larsmans
else:
i += 1
The main point is that you can't modify the parameters of a for loop while the loop is executing. From the python documentation:
It is not safe to modify the sequence being iterated over in the loop
(this can only happen for mutable sequence types, such as lists). If
you need to modify the list you are iterating over (for example, to
duplicate selected items) you must iterate over a copy.
Edit: if based on the comments you are trying to walk URLs, you need more complicated logic to do a depth-first or breadth-first walk than just replacing one list (the top-level links) with another list (links in the first page). In your example you completely lose track of pages 2 and 3 after diving into page 1.
The issue is that the assignment
a = [4,5,6]
just changes the variable a, not the underlying object. There are various ways you could deal with this; one would be to use a while loop like
a = [1,2,3]
i = 0
while i<len(a):
print a[i]
a = [4,5,6]
i += 1
prints
1
5
6
If you print id(a) at useful points in your code you'll realise why this doesn't work.
Even something like this does not work:
a = [1,2,3]
def change_a(new_val):
a = new_val
for i in a:
change_a([4,5,6])
print i
I don't think it is possible to do what you want. Break out of the current loop and start a new one with your new value of a.

Python list + list vs. list.append()

Today I spent about 20 minutes trying to figure out why
this worked as expected:
users_stories_dict[a] = s + [b]
but this would have a None value:
users_stories_dict[a] = s.append(b)
Anyone know why the append function does not return the new list? I'm looking for some sort of sensible reason this decision was made; it looks like a Python novice gotcha to me right now.
append works by actually modifying a list, and so all the magic is in side-effects. Accordingly, the result returned by append is None. In other words, what one wants is:
s.append(b)
and then:
users_stories_dict[a] = s
But, you've already figured that much out. As to why it was done this way, while I don't really know, my guess is that it might have something to do with a 0 (or false) exit value indicating that an operation proceeded normally, and by returning None for functions whose role is to modify their arguments in-place you report that the modification succeeded.
But I agree that it would be nice if it returned the modified list back. At least, Python's behavior is consistent across all such functions.
The append() method returns a None, because it modifies the list it self by adding the object appended as an element, while the + operator concatenates the two lists and return the resulting list
eg:
a = [1,2,3,4,5]
b = [6,7,8,9,0]
print a+b # returns a list made by concatenating the lists a and b
>>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
print a.append(b) # Adds the list b as element at the end of the list a and returns None
>>> None
print a # the list a was modified during the last append call and has the list b as last element
>>> [1, 2, 3, 4, 5, [6, 7, 8, 9, 0]]
So as you can see the easiest way is just to add the two lists together as even if you append the list b to a using append() you will not get the result you want without additional work

Categories