Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Map a function over recursive iterables

I have an arbitrarily nested iterable like so:

numbers = (1, 2, (3, (4, 5)), 7)

and I'd like to map a function over it without changing the structure. For example, I might want to convert all the numbers to strings to get

strings = recursive_map(str, numbers)
assert strings == ('1', '2', ('3', ('4', '5')), '7')

Is there a nice way to do this? I can imaging writing my own method to manually traverse numbers, but I'd like to know if there's a general way to map over recursive iterables.

Also, in my example, it's okay if strings gives me nested lists (or some iterable) rather nested tuples.

like image 948
Veech Avatar asked Feb 07 '17 16:02

Veech


People also ask

What does map () return in Python?

map() applies function to each item in iterable in a loop and returns a new iterator that yields transformed items on demand. function can be any Python function that takes a number of arguments equal to the number of iterables you pass to map() .

Can I use map on a list in Python?

In Python, you can use map() to apply built-in functions, lambda expressions ( lambda ), functions defined with def , etc., to all items of iterables such as lists and tuples.

Is map faster than for loop?

map() works way faster than for loop. Considering the same code above when run in this ide. Using map():

Will the functions map work on strings in Python?

You can use the Python map() function with a String. While using the map() with a String, the latter will act like an array. You can then use the map() function as an iterator to iterate through all the string characters.


1 Answers

We scan every element in the sequence, and proceeds into deeper recursion if the current item is a sub-sequence, or yields it's mapping if we reached a non-sequence data type (could be int, str, or any complex classes).

We use collections.Sequence to generalize the idea for every sequence, and not only tuples or lists, and type(item) upon yield to ensure that the sub-sequences we get back remains of the same type they were.

from collections import Sequence

def recursive_map (seq, func):
    for item in seq:
        if isinstance(item, Sequence):
            yield type(item)(recursive_map(item, func))
        else:
            yield func(item)

Demo:

>>> numbers = (1, 2, (3, (4, 5)), 7)
>>> mapped = recursive_map(numbers, str)
>>> tuple(mapped)
('1', '2', ('3', ('4', '5')), '7')

Or a more complex example:

>>> complex_list = (1, 2, [3, (complex('4+2j'), 5)], map(str, (range(7, 10))))
>>> tuple(recursive_map(complex_list, lambda x: x.__class__.__name__))
('int', 'int', ['int', ('complex', 'int')], 'map')
like image 69
Uriel Avatar answered Sep 23 '22 06:09

Uriel