Python Recursive Example Explanation - python

So currently working through MIT's OpenCourseWare computer science course online and I am having trouble trying to understand one of the recursive examples.
def f(L):
result = []
for e in L:
if type(e) != list:
result.append(e)
else:
return f(e)
return result
When the following input is given:
print f([1, [[2, 'a'], ['a','b']], (3, 4)])
The output is:
[2, 'a']
I am having trouble trying to understand how this function actually works or what it is doing. Shouldn't the function eventually be adding every string or int into the result list? I just need help with trying to understand how this function "winds up" and "unwinds"
I feel like the output should be:
[1,2,'a','a','b',3,4]
Any help would be appreciated thanks!

The function f returns a (shallow) copy of the first flat list it encounters with depth first search.
Why? Well first let us take a look at the base case: a list that contains no lists. Like [1,'a',2,5]. In that case the the if statement will always succeed, and therefore all elements of e will be added to the result and the result is returned.
Now what about the recursive case. This means there is an element that is a list. Like for instance [1,['a',2],5]. Now for the first element, the if succeeds, so 1 is added to the result list. But for the second element ['a',2] the if fails. This means we perform a recursive call on f with ['a',2]. Now since that list does not contain any sublists, we know it will return a copy of that list.
Note however that we immediately return the result of that recursive call. So from the moment we take the else branch, the result is of no importance anymore: we will return what that f(e) returns.
If we make the assumption we cannot construct a loop of infinite deep sublists (actually we can, but in that case we will get a stack overflow exception), we will eventually obtain a flat list and obtain that copy.
Example: If we take your sample input [1, [[2, 'a'], ['a','b']], (3, 4)]. We can trace the calls. So we first call f on that list, it will generate the following "trace":
# **trace** of an example function call
f([1, [[2, 'a'], ['a','b']], (3, 4)]):
result = []
# for 1 in L:
# if type(1) == list: # fails
# else
result.append(1) # result is now [1]
# for [[2,'a'],['a','b']] in L:
# if type([[2,'a'],['a','b']]) == list: succeeds
return f([[2,'a'],['a','b']])
result = []
# for [2,'a'] in L:
# if type([2,'a']) == list: succeeds
return f([2,'a'])
result = []
# for 2 in L:
# if type(2) == list: fails
# else:
result.append(2) # result is now [2]
# for 'a' in [2,'a']:
# if type('a') == list: fails
# else:
result.append('a') # result is now [2,'a']
return [2,'a']
return [2,'a']
return [2,'a']
Flattening:
Given you wanted to flatten the list instead of returning the first flat list, you can rewrite the code to:
def f(L):
result = []
for e in L:
if type(e) != list:
result.append(e)
else:
result += f(e)
return result
Note that this will only flatten lists (and not even subclasses of lists).

so by your suggested answer I see that you understand about the idea of the code. It digs deeper and deeper until it finds an element. But look about the back-step to the upper levels:
When it reaches the deepest point for the first time (the elements of the [2,'a'] list) it finish the loop on this level and returns the results 2 and a. And here is a RETURN statement ... that means the loop is stoped and thus no other elements are found.
The open question is now, why it is not showing 1 as part of result ? For the same reason, the RETURN is the result of the lower levels (2,a) and the result of the upper level. If you change "result" to a global variable, the outcome will be [1, 2, 'a']
best regards

The function as posted returns/exits when running into a first bottom-down list-element which doesn't contain a list - this prevents walking all the further branches of the recursion. For example:
print( f([1, [[2, 'a', [3, 'b', [4, 'c']]], ['a','b']], (3, 4)]) )
# gives: [4, 'c']
print( f([1, ['X','Y'], [[2, 'a', [3, 'b', [4, 'c']]], ['a','b']], (3, 4)]) )
# gives: ['X','Y']
The key point causing this behaviour is the line
result = []
This resets the list with results on each call of the function to an empty list. This way only one item is returned up from the chain of the recursion calls.
By the way the function f below does what you have expected, doesn't it?
def f(L, result):
for e in L:
if type(e) != list:
result.append(e)
else:
f(e, result)
result=[]; f([1, [[2, 'a', [3, 'b', [4, 'c']]], ['a','b']], (3, 4)], result) print( result )
# gives: [1, 2, 'a', 3, 'b', 4, 'c', 'a', 'b', (3, 4)]
result=[]; f( [1, ['X','Y'], [[2, 'a', [3, 'b', [4, 'c']]], ['a','b']], (3, 4)], result); print( result )
# gives: [1, 'X', 'Y', 2, 'a', 3, 'b', 4, 'c', 'a', 'b', (3, 4)]
NOTICE: (3,4) is a TUPLE not a list ...
The function f as above collects items from a list if these items are not a list themselves. In the special case, when the item in the list is a list, the function calls itself to collect the items from this list. This way all they way down of the hierarchy every element is collected, no matter how deep one needs to dig down. This is the beauty of recursion - a function calling itself does the 'magic' of visiting all of the branches and their leaves down a tree :)

Related

Python create a list that contains tuples [duplicate]

For example, I have three lists (of the same length)
A = [1,2,3]
B = [a,b,c]
C = [x,y,z]
and i want to merge it into something like:
[[1,a,x],[2,b,y],[3,c,z]].
Here is what I have so far:
define merger(A,B,C):
answer =
for y in range (len(A)):
a = A[y]
b = B[y]
c = C[y]
temp = [a,b,c]
answer = answer.extend(temp)
return answer
Received error:
'NoneType' object has no attribute 'extend'
It looks like your code is meant to say answer = [], and leaving that out will cause problems. But the major problem you have is this:
answer = answer.extend(temp)
extend modifies answer and returns None. Leave this as just answer.extend(temp) and it will work. You likely also want to use the append method rather than extend - append puts one object (the list temp) at the end of answer, while extend appends each item of temp individually, ultimately giving the flattened version of what you're after: [1, 'a', 'x', 2, 'b', 'y', 3, 'c', 'z'].
But, rather than reinventing the wheel, this is exactly what the builtin zip is for:
>>> A = [1,2,3]
>>> B = ['a', 'b', 'c']
>>> C = ['x', 'y', 'z']
>>> list(zip(A, B, C))
[(1, 'a', 'x'), (2, 'b', 'y'), (3, 'c', 'z')]
Note that in Python 2, zip returns a list of tuples; in Python 3, it returns a lazy iterator (ie, it builds the tuples as they're requested, rather than precomputing them). If you want the Python 2 behaviour in Python 3, you pass it through list as I've done above. If you want the Python 3 behaviour in Python 2, use the function izip from itertools.
To get a list of lists, you can use the built-in function zip() and list comprehension to convert each element of the result of zip() from a tupleto a list:
A = [1, 2, 3]
B = [4, 5, 6]
C = [7, 8, 9]
X = [list(e) for e in zip(A, B, C,)]
print X
>>> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Assuming you are doing this for class and not learning all of the tricks that make Python a great tool here is what you need. You had two problems, first if you want to extend then you do it in place but your desired result shows that you want to append, not extend
def merger(A,B,C):
answer = []
for y in range (len(A)):
a=A[y]
b=B[y]
c=C[y]
temp = [a,b,c]
answer.append(temp)
return answer
>>> answer
[[1, 'a', 'x'], [2, 'b', 'y'], [3, 'c', 'z']]
I was just wondering the same thing. I'm a total noob using code academy. This is what i came up to combine two lists index at index
toppings = ['pepperoni', 'pineapple', 'cheese', 'sausage', 'olives', 'anchovies', 'mushrooms']
prices = [2,6,1,3,2,7,2]
num_pizzas = len(toppings)
print("We sell "+str(num_pizzas)+" different kinds of pizza!")
***pizzas = list(zip(toppings, prices))***
print (pizzas)
the list pizzas printed out ...[('pepperoni', 2), ('pineapple', 6), ('cheese', 1), ('sausage', 3), ('olives', 2), ('anchovies', 7), ('mushrooms', 2)]

How two consecutive yield statement work in python? [duplicate]

This question already has answers here:
What does the "yield" keyword do in Python?
(51 answers)
Closed 5 months ago.
I stumble upon this code from pymotw.com in merging and splitting section.
from itertools import *
def make_iterables_to_chain():
yield [1, 2, 3]
yield ['a', 'b', 'c']
for i in chain.from_iterable(make_iterables_to_chain()):
print(i, end=' ')
print()
I can not understand how make_iterables_to_chain() is working. It contains two yield statement, how does it work?
I know how generators work but there but there was only single yield statement.
Help, please!
The same way a single yield works.
You can have as many yields as you like in a generator, when __next__ is called on it, it will execute until it bumps into the next yield. You then get back the yielded expression and the generator pauses until it's __next__ method is invoked again.
Run a couple of next calls on the generator to see this:
>>> g = make_iterables_to_chain() # get generator
>>> next(g) # start generator, go to first yield, get result
[1, 2, 3]
>>> next(g) # resume generator, go to second yield, get result
['a', 'b', 'c']
>>> # next(g) raises Exception since no more yields are found
A generator effectively allows a function to return multiple times. Every time a yield statement is executed, the value is returned to the caller, and the caller can continue the function's execution.
Usually, they are used as iterables in for loops.
The following function increments every element in an iterable by an amount:
def inc_each(nums, inc):
for i in nums:
yield i + inc
Here is an example of the usage:
gen = inc_each([1, 2, 3, 4], 100)
print(list(gen)) # [101, 102, 103, 104]
list is used here to convert an arbitrary iterable (in this case a generator) to a list.
The function you describe executes two yield statements:
def make_iterables_to_chain():
yield [1, 2, 3]
yield ['a', 'b', 'c']
If you call it, it returns a generator that, if iterated through, yields the lists [1, 2, 3] and ['a', 'b', 'c'].
gen = make_iterables_to_chain()
print(list(gen)) # [[1, 2, 3], ['a', 'b', 'c']]
itertools.chain.from_iterable will take a (possibly infinite) iterable of iterables and "flatten" it, returning a (possible infinite) iterable as the result.
Here is a way it could be implemented:
def from_iterable(iterables):
for iterable in iterables:
for i in iterable:
yield i
Just adding on top of the previous answers that when using such code:
def my_yield_function():
yield 1
yield 'test'
yield 2
yield true
You can unpack all the value with such code:
w,x,y,z = my_yield_function()

Python: how multiple arguments work?

I'm new to Python and programming. Can someone explain the following codes in details?
def myzip(*seqs):
seqs = [list(S) for S in seqs]
res = []
while all(seqs):
res.append(tuple(S.pop(0) for S in seqs))
return res
>>> myzip([1, 2, 3], ['a', 'b', 'c'])
[(1, 'a'), (2, 'b'), (3, 'c')]
Especially, I don't understand the S is for element in a list (e.g. 1, 2...) or the list ([1, 2, 3]).
I think I need a detail explanation for each line.
In the list comprehension, S is assigned each of the arguments passed to the function; seqs is a list of arguments passed in, and you passed in two lists. So S is first bound to [1, 2, 3] then ['a', 'b', 'c'].
>>> seqs = [[1, 2, 3], ['a', 'b', 'c']]
>>> seqs[0]
[1, 2, 3]
The first line just makes sure that all arguments are turned into lists, explicitly, so that you can later on call list.pop(0) on each. This allows you to pass in strings, tuples, dictionaries, or any other iterable as an argument to this function:
>>> myzip('123', 'abc')
[('1', 'a'), ('2', 'b'), ('3', 'c')]
The while all(seqs): loop then iterates until there is at least one argument that is empty. In other words, the loop terminates when the shortest sequence has been exhausted:
>>> myzip('1', 'abc')
[('1', 'a')]
In the loop, the first element of each of the input arguments is removed from the list and added to res as a tuple. For the 2 input lists, that means that first (1, 'a') is added to res, followed by (2, 'b') then (3, 'c').
seqs is the list of two separate lists: [1,2,3] and ['a', 'b', 'c']
Now while all(seqs): will iterate through the elements of seqs - the two lists mentioned above.
We then create an empty list res and append to it tuple objects.
Each tuple object will progressively contain the first element of each of the list in seqs. pop(0) will return the first element and remove it from the list thus changing the list in place (lists are mutable).
Thus what you are doing is you are creating a list of tuples obtained by pairing the corresponding elements in both the lists.
When you say seqs = [list(S) for S in seqs], S refers to each of the list element in seqs. However, in this particular call to the function, since you are passing lists as elements this statement becomes redundant.
First You need to know what is zip function. Because this function is doing the same job as zip in python.
def myzip(*seqs):
First line says this function gets as many argument you want and all of them will be gather in one list as seqs. Usage like myzip([1, 2, 3], ['a', 'b', 'c']) gives you seqs = [[1, 2, 3], ['a', 'b', 'c']].
seqs = [list(S) for S in seqs]
Then you want to make sure every item in seqs are list items. This line convert every item to list. This is what list does. (Even '123' to ['1', '2', '3'])
res = []
while all(seqs):
res.append(tuple(S.pop(0) for S in seqs))
return res
In these four lines it pops first element of each S of seqs and creates a tuple for final result. The final result is a list (res = []).
In the loop condition: all(seqs) it checks if all elements of seqs are available. If one them goes empty it breaks the loop.
In side the loop, pop(0) removes the first element from S and return it as value S.pop(0). This way it updates all elements of seqs. for next loop.
tuple creates tuple like (1, 'a') out of all first elements. Next iteration is going to be `(2, 'b') because all first elements popped before.
All these tuples in a list res is its goal. res.append adds these tuple to the final result.

Nested List and count()

I want to get the number of times x appears in the nested list.
if the list is:
list = [1, 2, 1, 1, 4]
list.count(1)
>>3
This is OK. But if the list is:
list = [[1, 2, 3],[1, 1, 1]]
How can I get the number of times 1 appears? In this case, 4.
>>> L = [[1, 2, 3], [1, 1, 1]]
>>> sum(x.count(1) for x in L)
4
itertools and collections modules got just the stuff you need (flatten the nested lists with itertools.chain and count with collections.Counter
import itertools, collections
data = [[1,2,3],[1,1,1]]
counter = collections.Counter(itertools.chain(*data))
print counter[1]
Use a recursive flatten function instead of itertools.chain to flatten nested lists of arbitrarily level depth
import operator, collections
def flatten(lst):
return reduce(operator.iadd, (flatten(i) if isinstance(i, collections.Sequence) else [i] for i in lst))
reduce with operator.iadd has been used instead of sum so that the flattened is built only once and updated in-place
Here is yet another approach to flatten a nested sequence. Once the sequence is flattened it is an easy check to find count of items.
def flatten(seq, container=None):
if container is None:
container = []
for s in seq:
try:
iter(s) # check if it's iterable
except TypeError:
container.append(s)
else:
flatten(s, container)
return container
c = flatten([(1,2),(3,4),(5,[6,7,['a','b']]),['c','d',('e',['f','g','h'])]])
print(c)
print(c.count('g'))
d = flatten([[[1,(1,),((1,(1,))), [1,[1,[1,[1]]]], 1, [1, [1, (1,)]]]]])
print(d)
print(d.count(1))
The above code prints:
[1, 2, 3, 4, 5, 6, 7, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
1
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
12
Try this:
reduce(lambda x,y: x+y,list,[]).count(1)
Basically, you start with an empty list [] and add each element of the list list to it. In this case the elements are lists themselves and you get a flattened list.
PS: Just got downvoted for a similar answer in another question!
PPS: Just got downvoted for this solution as well!
If there is only one level of nesting flattening can be done with this list comprenension:
>>> L = [[1,2,3],[1,1,1]]
>>> [ item for sublist in L for item in sublist ].count(1)
4
>>>
For the heck of it: count to any arbitrary nesting depth, handling tuples, lists and arguments:
hits = lambda num, *n: ((1 if e == num else 0)
for a in n
for e in (hits(num, *a) if isinstance(a, (tuple, list)) else (a,)))
lst = [[[1,(1,),((1,(1,))), [1,[1,[1,[1]]]], 1, [1, [1, (1,)]]]]]
print sum(hits(1, lst, 1, 1, 1))
15
def nested_count(lst, x):
return lst.count(x) + sum(
nested_count(l,x) for l in lst if isinstance(l,list))
This function returns the number of occurrences, plus the recursive nested count in all contained sub-lists.
>>> data = [[1,2,3],[1,1,[1,1]]]
>>> print nested_count(data, 1)
5
The following function will flatten lists of lists of any depth(a) by adding non-lists to the resultant output list, and recursively processing lists:
def flatten(listOrItem, result = None):
if result is None: result = [] # Ensure initial result empty.
if type(listOrItem) != type([]): # Handle non-list by appending.
result.append(listOrItem)
else:
for item in listOrItem: # Recursively handle each item in a list.
flatten(item, result)
return result # Return flattened container.
mylist = flatten([[1,2],[3,'a'],[5,[6,7,[8,9]]],[10,'a',[11,[12,13,14]]]])
print(f'Flat list is {mylist}, count of "a" is {mylist.count("a")}')
print(flatten(7))
Once you have a flattened list, it's a simple matter to use count on it.
The output of that code is:
Flat list is [1, 2, 3, 'a', 5, 6, 7, 8, 9, 10, 'a', 11, 12, 13, 14], count of "a" is 2
[7]
Note the behaviour if you don't pass an actual list, it assumes you want a list regardless, one containing just the single item.
If you don't want to construct a flattened list, you can just use a similar method to get the count of any item in the list of lists, with something like:
def deepCount(listOrItem, searchFor):
if type(listOrItem) != type([]): # Non-list, one only if equal.
return 1 if listOrItem == searchFor else 0
subCount = 0 # List, recursively collect each count.
for item in listOrItem:
subCount += deepCount(item, searchFor)
return subCount
deepList = [[1,2],[3,'a'],[5,[6,7,[8,9]]],[10,'a',[11,[12,13,14]]]]
print(f'Count of "a" is {deepCount(deepList, "a")}')
print(f'Count of 13 is {deepCount(deepList, 13)}')
print(f'Count of 99 is {deepCount(deepList, 99)}')
As expected, the output of this is:
Count of "a" is 2
Count of 13 is 1
Count of 99 is 0
(a) Up to the limits imposed by Python itself of course, limits you can increase by just adding this to the top of your code:
import sys
sys.setrecursionlimit(1001) # I believe default is 1000.
I mention that just in case you have some spectacularly deeply nested structures but you shouldn't really need it. If you're nesting that deeply then you're probably doing something wrong :-)

how to get the index or the element itself of an element found with "if element in list"

Does a direct way to do this exists?
if element in aList:
#get the element from the list
I'm thinking something like this:
aList = [ ([1,2,3],4) , ([5,6,7],8) ]
element = [5,6,7]
if element in aList
#print the 8
L = [([1, 2, 3], 4), ([5, 6, 7], 8)]
element = [5, 6, 7]
for a, b in L:
if a == element:
print b
break
else:
print "not found"
But it sounds like you want to use a dictionary:
L = [([1, 2, 3], 4), ([5, 6, 7], 8)]
element = [5, 6, 7]
D = dict((tuple(a), b) for a, b in L)
# keys must be hashable: list is not, but tuple is
# or you could just build the dict directly:
#D = {(1,2,3): 4, (5,6,7): 8}
v = D.get(tuple(element))
if v is not None:
print v
else:
print "not found"
Note that while there are more compact forms using next below, I imagined the reality of your code (rather than the contrived example) to be doing something at least slightly more complicated, so that using an block for the if and else becomes more readable with multiple statements.
(Note: this answer refers to the question text, not the example given in the code, which doesn't quite match.)
Printing the element itself doesn't make any sense, because you already have it in the test:
if element in lst:
print element
If you want the index, there's an index method:
if element in lst:
print lst.index(element)
And, on the off chance that you're asking this because you want to loop through a list and do things with both the value and the index, be sure to use the enumerate idiom:
for i, val in enumerate(lst):
print "list index": i
print "corresponding value": val
>>> aList = [ ([1,2,3],4) , ([5,6,7],8) ]
>>> element = [5,6,7]
if you only wish to check if the first element is present
>>> any(element==x[0] for x in aList)
True
to find the corresponding value
>>> next(x[1] for x in aList if element==x[0])
8
>>> aList = [ ([1,2,3],4) , ([5,6,7],8) ]
>>> for i in aList:
... if [5,6,7] in i:
... print i[-1]
...
8
[5, 6, 7] is not an item of the aList you show, so the if will fail, and your question as posed just doesn't pertain. More generally, the loop implied in such an if tosses away the index anyway. A way to make your code snippet work would be, instead of the if, to have something like (Python 2.6 or better -- honk if you need to work on different versions):
where = next((x for x in aList if x[0] == element), None)
if where:
print(x[1])
More generally, the expressions in the next and in the print must depend on the exact "fine grained" structure of aList -- in your example, x[0] and x[1] work just fine, but in a slightly different example you may need different expressions. There is no "generic" way that totally ignores how your data is actually structured and "magically works anyway"!-)
The code in your question is sort of weird. But, assuming you're learning the basics:
Getting the index of an element:
it's actually simple: list.index(element). Assuming of course, the element only appears once. If it appears more than once, you can use the extra parameters:
list.index(element, start_index): here it will start searching from start_index. There's also:
list.index(element, start_index, end_index): I think it's self explanitory.
Getting the index in a for loop
If you're looping on a list and you want to loop on both the index and the element, the pythonic way is to enumerate the list:
for index, element in enumerate(some_list):
# here, element is some_list[index]
Here, enumerate is a function that takes a list and returns a list of tuples. Say your list is ['a', 'b', 'c'], then enumerate would return: [ (1, 'a'), (2, 'b'), (3, 'c') ]
When you iterate over that, each item is a tuple, and you can unpack that tuple.
tuple unpacking is basically like this:
>>> t = (1, 'a')
>>> x, y = t
>>> t
(1, 'a')
>>> x
1
>>> y
'a'
>>>
One possible solution.
aList = [ ([1,2,3],4) , ([5,6,7],8) ]
element = [5,6,7]
>>> print(*[y for x,y in aList if element == x])
8

Categories