Python: сопоставление функции с рекурсивными итерациями
у меня есть произвольно вложенная итерация, например:
numbers = (1, 2, (3, (4, 5)), 7)
и я хотел бы отобразить функцию над ней без изменения структуры. Например, я мог бы преобразовать все числа в строки, чтобы получить
strings = recursive_map(str, numbers)
assert strings == ('1', '2', ('3', ('4', '5')), '7')
есть ли хороший способ сделать это? Я могу написать свой собственный метод, чтобы вручную пересечь numbers
, но я хотел бы знать, есть ли общий способ отображения рекурсивных итераций.
кроме того, в моем примере, это нормально, если strings
дает me вложенные списки (или некоторые итерационные), а не вложенные кортежи.
4 ответов
мы сканируем каждый элемент в последовательности и переходим к более глубокой рекурсии, если текущий элемент является подпоследовательностью или дает его отображение, если мы достигли типа данных без последовательности (может быть int
, str
, или любые сложные классы).
мы используем:collections.Sequence
обобщить идеи для каждой последовательности, а не только кортежей или списков, и type(item)
при выходе, чтобы убедиться, что суб-последовательности мы получаем обратно остается того же типа они были.
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)
демо:
>>> numbers = (1, 2, (3, (4, 5)), 7)
>>> mapped = recursive_map(numbers, str)
>>> tuple(mapped)
('1', '2', ('3', ('4', '5')), '7')
или более сложный пример:
>>> 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')
def recursive_map(f, it):
return (recursive_map(f, x) if isinstance(x, tuple) else f(x) for x in it)
если вы хотите расширить свой результат dict
, set
и другие, вы можете использовать ответ Уриэля:
from collections import Sequence, Mapping
def recursive_map(data, func):
apply = lambda x: recursive_map(x, func)
if isinstance(data, Mapping):
return type(data)({k: apply(v) for k, v in data.items()})
elif isinstance(data, Sequence):
return type(data)(apply(v) for v in data)
else:
return func(data)
тесты входного сигнала:
recursive_map({0: [1, {2, 2, 3}]}, str)
выходы:
{0: ['1', '{2, 3}']}
я расширил понятие рекурсивной карты для работы со стандартными коллекциями python: list, dict, set, tuple:
def recursiveMap(something, func):
if isinstance(something, dict):
accumulator = {}
for key, value in something.items():
accumulator[key] = recursiveMap(value, func)
return accumulator
elif isinstance(something, (list, tuple, set)):
accumulator = []
for item in something:
accumulator.append(recursiveMap(item, func))
return type(something)(accumulator)
else:
return func(something)
это проходит следующие тесты, которые я буду включать в основном в качестве примеров использования:
from hypothesis import given
from hypothesis.strategies import dictionaries, text
from server.utils import recursiveMap
def test_recursiveMap_example_str():
assert recursiveMap({'a': 1}, str) == {'a': '1'}
assert recursiveMap({1: 1}, str) == {1: '1'}
assert recursiveMap({'a': {'a1': 12}, 'b': 2}, str) == {'a': {'a1': '12'}, 'b': '2'}
assert recursiveMap([1, 2, [31, 32], 4], str) == ['1', '2', ['31', '32'], '4']
assert recursiveMap((1, 2, (31, 32), 4), str) == ('1', '2', ('31', '32'), '4')
assert recursiveMap([1, 2, (31, 32), 4], str) == ['1', '2', ('31', '32'), '4']
@given(dictionaries(text(), text()))
def test_recursiveMap_noop(dictionary):
assert recursiveMap(dictionary, lambda x: x) == dictionary