Recursively modify list with irregular shape - python

The aa list is my input, and the bb list my desired output. The operation is rather simple (change every element in aa to string appending some characters, while keeping the original shape), but the way I do it seems unnecessarily convoluted.
Is there a better (more Pythonic) way to do this?
aa = [0, 5, 6, [8], 3, [9, 2]]
bb = []
for e1 in aa:
if type(e1) == list:
bb2 = []
for e2 in e1:
bb2.append('sss' + str(e2))
bb.append(bb2)
else:
bb.append('sss' + str(e1))
print(bb)
['sss0', 'sss5', 'sss6', ['sss8'], 'sss3', ['sss9', 'sss2']]

You can use a recursive list comprehension to achieve this
def f(data):
return [f(i) if isinstance(i, list) else 'sss{}'.format(i) for i in data]
For example
>>> aa = [0, 5, 6, [8], 3, [9, 2]]
>>> f(aa)
['sss0', 'sss5', 'sss6', ['sss8'], 'sss3', ['sss9', 'sss2']]

You could do what your question title is already hinting at, i.e. use recursion:
def transform(ll):
if isinstance(ll, list):
return list(map(transform, ll))
else:
return 'sss%s' % ll
print(transform([0, 5, 6, [8], 3, [9, 2]]))

Well, one way to do this recursively that is a little more concise is
def addSSS(item):
if isinstance(item, list):
# map takes a function, and a list and returns a
# new list that is the result of applying the function
# to each element. list() converts the result to a list
return list(map(addSSS, item))
# if it's not a list simply append the string and return that value
return "sss" + str(item)
bb = list(map(addSSS, aa))
Maps are nice because they don't evaluate each element right away, only on demand, of course here it gets evaluated right away when you call list() to turn it back to a list
This has the additional advantage that it works when your list nests deeper like aa = [1, 2, [1, 2, [1, 2]]]

Related

How can I loop through a nested list?

there is an exercise I was assigned to solve a week ago and I can't seem to find a solution to it. I want to create a Python function which receives a list and returns every element inside the list in a new one (even if there are nested elements inside it).
def ListOfLists(lista):
listaUnica = []
if(type(lista) != list):
return None
for i in lista:
if (type(i) != list):
listaUnica.append(i)
elif(type(i) == list):
for elemento in i:
if(type(elemento) == list):
listaUnica.append(elemento)
continue
elif(type(elemento) != list):
listaUnica.append(elemento)
return listaUnica
This is the best function I came up with. The problem that I have with this function is that it doesn't analyse while looping though the list if an element is indeed a list, if it turns out true, loop through that nested list and so on until there are no more nested lists. While doing so I want it to return the elements in the same order that they were assigned originally.
Let's suppose:
lista = [1,2,[3, [4, 5, [6]], 7]
ListOfLists(lista)
Expected output:
[1, 2, 3, 4, 5, 6, 7]
Or another example:
lista = [1,2,['a','b'],[10]]
ListOfLists(lista)
Expected output:
[1, 2, 'a', 'b', 10]
I'm not allowed to use any framework or library.
You'll need a recursive function (a function that calls itself) to walk through any depth of nested lists.
If you're allowed (and know how) to write a generator function,
def flatten_lists(lista):
for item in lista:
if isinstance(item, list):
yield from flatten_lists(item)
else:
yield item
lista = [1, 2, [3, [4, 5, [6]], 7]]
print(list(flatten_lists(lista)))
does the trick quite elegantly.
Without using generators, you'd do
def flatten_lists(lista):
output = []
for item in lista:
if isinstance(item, list):
output.extend(flatten_lists(item))
else:
output.append(item)
return output
To emulate the recursion by hand (i.e. without a real recursive function), you could do
def flatten_lists(lista):
queue = [lista]
output = []
while queue:
item = queue.pop(0)
if isinstance(item, list):
queue = item + queue
else:
output.append(item)
return output
Recursive method
def flatten(list_of_lists):
if len(list_of_lists) == 0:
return list_of_lists
if isinstance(list_of_lists[0], list):
return flatten(list_of_lists[0]) + flatten(list_of_lists[1:])
return list_of_lists[:1] + flatten(list_of_lists[1:])
print(flatten([[1, 2, 3, 4], [5, 6, 7], [8, 9, [10]], 11]))

Using recursion to determine the index path of a nested function

Im trying to make a function which finds a value from a list (xs) using another list (index_list) as an index path.
My function should work like this:
xs = [[[1, 2], 3], [4, 5, [6, 7]], 8, [9, 10, 11]]
>>> recursive_index(xs, [1, 2, 0])
6
So far I have:
def recursive_index(xs: List, index_path):
if not index_path:
return 0
return recursive_index(xs, index_path[1:])
This however just returns 0 for everything, but I don't know what else the base case should be.
You're quite close, but you forgot that at each recursion you actually need to index the list so that you get further in at each recursion. This way, by the time you get to the base case, the variable xs will store the correct result and you can just return it.
This is what the code would look like:
def recursive_index(xs: List, index_path):
if not index_path:
return xs
return recursive_index(xs[index_path[0]], index_path[1:])
You want this:
def recursive_index(xs, index_path):
if not index_path:
# if path is exhausted just return current element
return xs
# use first index on current list and recurse with the remaining path
return recursive_index(xs[index_path[0]], index_path[1:])
Your recursive function should keep extracting the value from xs at the first index of index_path until there is no more index in the rest of the path:
def recursive_index(xs, index_path):
index, *rest = index_path
value = xs[index]
return recursive_index(value, rest) if rest else value
The accepted answer already explains how to fix your recursive function.
Note that an iterative function works just as well:
def iterative_index(xs, index_path):
for idx in index_path:
xs = xs[idx]
return xs
Or using reduce:
from functools import reduce
def reduce_index(xs, index_path):
return reduce(list.__getitem__, index_path, xs)
Testing:
xs = [[[1, 2], 3], [4, 5, [6, 7]], 8, [9, 10, 11]]
index_path = (1, 2, 0)
print( iterative_index(xs, index_path) )
# 6
print( reduce_index(xs, index_path) )
# 6

How to get only even numbers from list

def only_evens(lst):
""" (list of list of int) -> list of list of int
Return a list of the lists in lst that contain only even integers.
>>> only_evens([[1, 2, 4], [4, 0, 6], [22, 4, 3], [2]])
[[4, 0, 6], [2]]
"""
even_lists = []
for sublist in lst:
for i in sublist:
if i % 2 == 0:
even_lists.append(i)
return even_lists
I can't do this because it returns everything in one list[]
But how can I return sublist that consists only with even integers?
I would split it into two functions: one which checks if a list contains only even numbers, and the other one is your main function (I renamed it to get_even_lists()), which gets all the even lists from a list of lists:
def only_even_elements(l):
""" (list of int) -> bool
Return a whether a list contains only even integers.
>>> only_even_elements([1, 2, 4]) # 1 is not even
False
"""
for e in l:
if e % 2 == 1:
return False
return True
def get_even_lists(lst):
""" (list of list of int) -> list of list of int
Return a list of the lists in lst that contain only even integers.
>>> only_evens([[1, 2, 4], [4, 0, 6], [22, 4, 3], [2]])
[[4, 0, 6], [2]]
"""
# return [l for l in lst if only_even_elements(l)]
even_lists = []
for sublist in lst:
if only_even_elements(sublist):
even_lists.append(sublist)
return even_lists
Although, this could be done with for/else:
def get_even_lists(lst):
""" (list of list of int) -> list of list of int
Return a list of the lists in lst that contain only even integers.
>>> only_evens([[1, 2, 4], [4, 0, 6], [22, 4, 3], [2]])
[[4, 0, 6], [2]]
"""
even_lists = []
for sublist in lst:
for i in sublist:
if i % 2 == 1:
break
else:
even_lists.append(sublist)
return even_lists
Or as others have suggested, a one-liner:
def get_even_lists(lst):
""" (list of list of int) -> list of list of int
Return a list of the lists in lst that contain only even integers.
>>> only_evens([[1, 2, 4], [4, 0, 6], [22, 4, 3], [2]])
[[4, 0, 6], [2]]
"""
return [sublst for sublst in lst if all(i % 2 == 0 for i in sublst)]
But let's be honest here: while it's arguable that using two functions might be a bit longer and not as "cool" as the other two solutions, it's reusable, easy to read and understand, and it's maintainable. I'd argue it's much better than any other option out there.
You could also do this using functional programming:
def only_evens(lst):
return filter(lambda ls: all(map(lambda n: not n & 1, ls)), lst)
EDIT
As per J.F. Sebastian's recommendation, I split this into three functions:
is_odd = lambda n: n & 1
all_even = lambda arr: not any(map(is_odd, arr))
only_evens = lambda arr: filter(all_even, arr)
is_odd one checks if a number is odd using bitwise operations for efficiency. all_even checks if a list has all even numbers and returns a boolean. only_evens takes a list of list of integers and returns a list of the lists that contain only even integers.
I would think not about what you are keeping, but about what you're removing (all lists containing an odd number). Something like
import copy
list_copy = copy.copy(lists)
def only_evens(list_copy):
for l in list_copy:
for i in l:
if i % 2 != 0:
list_copy.remove(l)
break
return(list_copy)
(which emphasizes readability over conciseness, to be sure.)
I guess you can divide your problem into small parts.
def contains_only_evens(lst):
return sum([x%2 for x in lst]) == 0
def only_evens(lst):
for sublist in lst:
if not contains_only_evens(sublist):
lst.remove(sublist)
return lst
In this way you can:
iterate over a list and its sublists
check if the list contains any odd integer
keep only sublist with no odds values

Recursively find the kth largest int in list of list of int in Python

As a newbie in programming, I am trying to do a function to find the kth largest int in a list of list of ints. I tried list of ints before and it worked.
However, for this function, its base case has too many possibilities:
Ex: [1, 2], [[], 1], [[1], 2], [[1], [1]]
I got stuck at the base case. Can anyone give me a hint for this?
This function would operate like:
find_2smallest([1,1,3])->1
find_2smallest([[1,[]], 9, [[1], [3]], [4]])->3
some hints:
write a flatten function to convert list hierarchy to a single collection (with unique elements), sort and pick the k'th element.
sample implementation
def flatten(t):
for e in t:
if isinstance(e,list):
yield from flatten(e)
else:
yield e
set(flatten([[1,[]], 9, [[1], [3]], [4]]))
{1, 3, 4, 9}
of course, this approach is not the most efficient (perhaps it's the least efficient). You can return the first k elements from each sub level and prune at k'th level at each merge.
I wrote a generalized solution (you specify k) using extra arguments with default values to track both the smallest set of k found so far, and whether this was the top level of the recursion or a sub-level. Sub-levels return the current smallest subset, the top level returns the last entry (which is the kth smallest value). The smallest set of k is maintained using heapq's nsmallest method.
import heapq
def find_kth_smallest(k, a_list, smallest_k = [], top_level = True):
l = len(a_list)
if l > 1:
l /= 2
smallest_k = find_kth_smallest(k, a_list[:l], smallest_k, False)
smallest_k = find_kth_smallest(k, a_list[l:], smallest_k, False)
elif l < 1:
return []
else:
if isinstance(a_list[0], list):
smallest_k = find_kth_smallest(k, a_list[0], smallest_k, False)
else:
smallest_k.append(a_list[0])
smallest_k = heapq.nsmallest(k, smallest_k)
if top_level:
return smallest_k[-1]
else:
return smallest_k
print find_kth_smallest(3, [10, [9, 8, [[]]], [7, 6, [[5], 4, 3]], 2, 1]) # => 3

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 :-)

Categories