Get all elements of nested lists with recursion - python

~ from This Edabit Challenge ~
I need to get all the elements of nested lists and put them all in one list using recursion.My code below prints out each element, but how do I save them all to one list and return them?
It has to be kept in the scope of the function. I can't add a global list and append all of them. It works technically, but it doesn't work for the challenge I'm trying to pass.
I printed the values out (which is var x in the code) because that shows me that I'm getting close (I think). I just need a way to return the values back to my function and have it append it to the list that I will eventually return.
Examples:
flatten([[[[[["direction"], [372], ["one"], [[[[[["Era"]]]], "Sruth", 3337]]], "First"]]]]) ➞ ["direction", 372, "one", "Era", "Sruth", 3337, "First"]
flatten([[4666], [5394], [466], [[["Saskia", [[[[["DXTD"]], "Lexi"]]]]]]]) ➞ [4666, 5394, 466, "Saskia", "DXTD", "Lexi"]
Code:
def flatten(arr):
res = []
if isinstance(arr, list):
for i in arr:
res.append(flatten(i))
else:
return arr
if isinstance(res, list):
for i in res:
x = flatten(i)
if x:
print(x)
x = flatten([[[[[["direction"], [372], ["one"], [[[[[["Era"]]]], "Sruth", 3337]]], "First"]]]])
print(main)
outputs :
direction
372
one
Era
Sruth
3337
First
[]
The output above shows that my code goes through every non-list value.

Variations of Hai Vu's solutions...
Their first solution uses nested generators, meaning every value gets yielded through that stack of generators. If the structure is deeply nested, this can make the solution take quadratic instead of linear time overall. An alternative is to create a local list in the main function and have the helper function fill it. I prefer using a nested function for that, so I don't have to pass the list around and don't expose the helper function to the outside.
def flatten(nested):
flat = []
def helper(nested):
for e in nested:
if isinstance(e, list):
helper(e)
else:
flat.append(e)
helper(nested)
return flat
Benchmark with 800 integers at depth 800:
26.03 ms Hai_Vu
0.25 ms Kelly
25.62 ms Hai_Vu
0.24 ms Kelly
26.07 ms Hai_Vu
0.24 ms Kelly
Their second solution uses a "queue" (but really treats it like a "reversed" stack, extending/popping only on the left). I think an ordinary stack (using a list) is more natural and simpler:
def flatten(nested):
stack = [nested]
out = []
while stack:
e = stack.pop()
if isinstance(e, list):
stack += reversed(e)
else:
out.append(e)
return out

Pass a list to flatten, and append to it at each step:
def flatten(arr, list_):
if isinstance(arr, list):
for i in arr:
flatten(i, list_)
else:
list_.append(arr)
test = [['a'], 'b']
output = []
flatten(test, output)
output
['a', 'b']
EDIT: If you want specifically to return the list, use
def flatten(arr, list_=None):
if list_ is None:
list_ = []
if isinstance(arr, list):
for i in arr:
flatten(i, list_)
else:
list_.append(arr)
return list_

I would like to offer two solutions: the first uses recursion and the second uses a queue.
First solution
def flatten_helper(nested):
for e in nested:
if isinstance(e, list):
yield from flatten_helper(e)
else:
yield e
def flatten(nested):
return list(flatten_helper(nested))
The flatten_helper function is a generator, which generates a list of elements that are not a list. If an element is a list, we call flatten_helper again until we get non-list elements.
Second solution
import collections
def flatten(nested):
queue = collections.deque(nested)
out = []
while queue:
e = queue.popleft()
if isinstance(e, list):
queue.extendleft(reversed(e))
else:
out.append(e)
return out
In this solution, we loop through the nested list. If the element is a list, we place each sub element into a queue for later processing. If the element is not a list, we append it to out.

Another possibility... more on the same wavelength of Hai Vu 1st solution:
def flatter(lst):
output = []
for i in lst:
if isinstance(i, list):
output.extend(flatter(i))
else:
output.append(i)
return output

Related

How can I retrieve the remaining items in a for loop in Python?

I have a simple for loop iterating over a list of items. At some point, I know it will break. How can I then return the remaining items?
for i in [a,b,c,d,e,f,g]:
try:
some_func(i)
except:
return(remaining_items) # if some_func fails i.e. for c I want to return [c,d,e,f,g]
I know I could just take my inital list and delete the the items from the beginning for every iteration one by one. But is there maybe some native Python function for this or something more elegant?
You can make use of enumerate that yields both the element and its index in the list.
myList = [a,b,c,d,e,f,g]
for index, item in enumerate(myList):
try:
some_func(item)
except:
return myList[index:]
Test-it online
You could use itertools.dropwhile, you use a test method to apply some_func, when it raises an error, the test becomes False and so it stops and dropwhile return the remaining ones
from itertools import dropwhile
def my_fct():
def test(v):
try:
some_func(v)
return False
except:
return True
return list(dropwhile(test, [a, b, c, d, e, f, g]))
def test():
myList = [1,2,3,4,0,7,8,9]
for index, item in enumerate(myList):
try:
print(index,item)
1/item
except:
return(myList[index:])

How to index into a list until a specific data type is encountered?

I have a list, L. L may contain other lists inside it. These other lists may also contain lists. At some point, the indexing will return an int value. How do I make a function that returns the first encountered int value?
L = [[[1,2,3],[4,5]],[6,7]]
As you can see, L can have lists of lists until some arbitrary endpoint, of which I am unaware. I tried first doing hardcoded if loops such as:
if isinstance(L[0],int): return L[0]
elif isinstance(L[0][0],int): return L[0][0]
... #and so on
But this feels unPythonic and wrong.
Is there a built-in function or other methodology for solving this problem?
As suggested by Barmar in the comments, you could write a recursive function (the problem itself is a classical use case for recursion). However, you could equally use a while loop.
Recursive version:
def first(L):
if isinstance(L, list):
return first(L[0])
else:
return L
Version with while-loop:
def first(L):
while isinstance(L, list):
L = L[0]
return L
Instead of using isinstance, you can also use a try-block instead:
def first(L):
try:
while True:
L = L[0]
except:
return L
A last variant would use iteration instead of subscripts like so:
def first(L):
try:
while True:
L, *_ = L
except:
return L
Comparing the running times of these variants, I get:
Recursive function: 0.462
While and isinstance: 0.355
While and try: 0.319
While and iter: 0.567
So, using a while-loop with a try-block is the fastest solution. And it has the nice added benefit that it also just works out of the box with tuples instead of lists.
You can use a simple loop:
my_elem = L[0]
while not isinstance(my_elem, int):
my_elem = my_elem[0]
This is the nice part of a dynamically typed language: you can always put an int in to the same variable you were putting lists before

Python: using a function inside a function?

So for an assignment I have to create a bunch of different functions in one Python file. One of the functions calls for inputting a list (sorted_list) and a string from that list (item). What the function does is reads the list and removes any duplicates of the specified string from the list.
def remove_duplicates(sorted_list, item):
list_real = []
for x in range(len(sorted_list)-1):
if(sorted_list[i] == item and sorted_list[i+1] == item):
list_real = list_real + [item]
i+1
else:
if(sorted_list[i] != item):
list_real = list_real + [sorted_list[i]]
i+=1
return list_real
So
remove_duplicates(['a','a','a','b','b','c'] 'a') would return ['a','b','b','c']
This probably isn't the most efficient way to do something like this, but that isn't my problem.
The next function I have to define is similar to the one above except it only takes sorted_list and it has to remove duplicates for each item instead of a specified one. The only thing I know is that you have to use a for loop that makes the remove_duplicates run for each item in a given list, but I have no idea how to actually implement a function inside of another function. Can anyone help me out?
This works nicely:
from itertools import ifilterfalse
def remove_duplicates(sorted_list, item):
idx = sorted_list.index(item)
list_real = sorted_list[:idx+1]
if len(list_real) != len(sorted_list):
for item in ifilterfalse (lambda x: x is item, sorted_list[idx:]):
list_real.append(item)
return list_real

Using recursion to create a linked list from a list

how would one go about using recursion in order to take a list of random values and make it a linked list? Where each value is a node. As of right now, i've tried implementing the following...
def pyListToMyList(pylst):
lists = mkMyList()
lists.head = pyListToMyListRec(pylst)
return lists
def pyListToMyList(pylst):
if pylst:
return mkEmptyNode()
else:
return mkNode(pylst[0], pyLstToMyListRec(pylst[1:]))
The problem is the the else statement which returns an error saying that the index is out of range.
def pyListToMyList(pylst):
if not pylst:
return mkEmptyNode()
else:
return mkNode(pylst[0], pyLstToMyListRec(pylst[1:]))
EDIT: Though this is O(n^2) because of all the list copying.
I would do
def pyListToMyList(pylst, i=0):
if i > len(pylst):
return mkEmptyNode()
else:
return mkNode(pylst[i], pyLstToMyListRec(pylst, i+1))
or even more efficient and less likely to overflow stack (though this does not use recursion):
def pyListToMyList(pylst):
lst = mkEmptyNode()
for x in reversed(pylist):
lst = mkNode(x, lst)
return lst

How to use Python iterators elegantly

I am trying to use iterators more for looping since I heard it is faster than index looping. One thing I am not sure is about how to treat the end of the sequence nicely. The way I can think of is to use try and except StopIteration, which looks ugly to me.
To be more concrete, suppose we are asked to print the merged sorted list of two sorted lists a and b. I would write the following
aNull = False
I = iter(a)
try:
tmp = I.next()
except StopIteration:
aNull = True
for x in b:
if aNull:
print x
else:
if x < tmp:
print x
else:
print tmp,x
try:
tmp = I.next()
except StopIteration:
aNull = True
while not aNull:
print tmp
try:
tmp = I.next()
except StopIteration:
aNull = True
How would you code it to make it neater?
I think handling a and b more symmetrically would make it easier to read. Also, using the built-in next function in Python 2.6 with a default value avoids the need to handle StopIteration:
def merge(a, b):
"""Merges two iterators a and b, returning a single iterator that yields
the elements of a and b in non-decreasing order. a and b are assumed to each
yield their elements in non-decreasing order."""
done = object()
aNext = next(a, done)
bNext = next(b, done)
while (aNext is not done) or (bNext is not done):
if (bNext is done) or ((aNext is not done) and (aNext < bNext)):
yield aNext
aNext = next(a, done)
else:
yield bNext
bNext = next(b, done)
for i in merge(iter(a), iter(b)):
print i
The following function generalizes the approach to work for arbitrarily many iterators.
def merge(*iterators):
"""Merges a collection of iterators, returning a single iterator that yields
the elements of the original iterators in non-decreasing order. Each of
the original iterators is assumed to yield its elements in non-decreasing
order."""
done = object()
n = [next(it, done) for it in iterators]
while any(v is not done for v in n):
v, i = min((v, i) for (i, v) in enumerate(n) if v is not done)
yield v
n[i] = next(iterators[i], done)
You're missing the whole point of iterators. You don't manually call I.next(), you just iterate through I.
for tmp in I:
print tmp
Edited
To merge two iterators, use the very handy functions in the itertools module. The one you want is probably izip:
merged = []
for x, y in itertools.izip(a, b):
if x < y:
merged.append(x)
merged.append(y)
else:
merged.append(y)
merged.append(x)
Edit again
As pointed out in the comments, this won't actually work, because there could be multiple items from list a smaller than the next item in list b. However, I realised that there is another built-in funciton that deals with this: heapq.merge.
The function sorted works with lists and iterators. Maybe it is not what you desire, but the following code works.
a.expand(b)
print sorted(iter(a))

Categories